From f406640d5e05d9cc6ec75ca8c2060273fab42061 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:35:50 +0000 Subject: [PATCH 001/220] Initial commit --- .gitignore | 21 +++++++++++++++++++++ README.md | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad67955 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# 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/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2c50d5 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Omega +A loadballancer and Omikron-manager for Tensamin From 38d1301ea4231044c30fd5f2189c76ffcdb75a59 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:35:50 +0000 Subject: [PATCH 002/220] Initial commit --- .gitignore | 21 +++++++++++++++++++++ README.md | 2 ++ 2 files changed, 23 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad67955 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# 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/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..a2c50d5 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Omega +A loadballancer and Omikron-manager for Tensamin From 5a069ab42bcbf9e2c99b002ac87c1f977f95a7ca Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:37:57 +0000 Subject: [PATCH 003/220] Added LICENSE --- LICENSE | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d218eff --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) [2025] [Methanium] +All rights reserved. + +This software is protected by copyright. Copying, editing, +distributing, publicly performing, or any other use of this software +or its components, in source or binary form, is strictly prohibited without the express +written permission of the copyright holder. + +FUTURE LICENSE ACCEPTANCE: +It is the copyright holder's intention to release this software in the future +under a license yet to be defined, which will, among other things, +allow private, non-commercial use. This statement does not constitute +a current license grant and does not alter the above +prohibition on use, copying, or modification. Until the formal +publication of such a future license, all rights remain +reserved. From 5149a2d7d1c301dfb9fd2f52aa1729eced61ab21 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 7 Dec 2025 13:37:57 +0000 Subject: [PATCH 004/220] Added LICENSE --- LICENSE | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d218eff --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) [2025] [Methanium] +All rights reserved. + +This software is protected by copyright. Copying, editing, +distributing, publicly performing, or any other use of this software +or its components, in source or binary form, is strictly prohibited without the express +written permission of the copyright holder. + +FUTURE LICENSE ACCEPTANCE: +It is the copyright holder's intention to release this software in the future +under a license yet to be defined, which will, among other things, +allow private, non-commercial use. This statement does not constitute +a current license grant and does not alter the above +prohibition on use, copying, or modification. Until the formal +publication of such a future license, all rights remain +reserved. From 5d599473241da702d054876b75e5fe55f575f8d8 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 3 Jan 2026 21:40:21 +0100 Subject: [PATCH 005/220] Creation --- .gitignore | 10 +- Cargo.lock | 4462 ++++++++++++++++++++++++++++++ Cargo.toml | 42 + src/data/communication.rs | 454 +++ src/data/mod.rs | 1 + src/main.rs | 44 + src/server/api.rs | 177 ++ src/server/mod.rs | 5 + src/server/omikron_connection.rs | 258 ++ src/server/omikron_manager.rs | 0 src/server/server.rs | 475 ++++ src/server/socket.rs | 58 + src/sql/iota_omikron_tracker.rs | 22 + src/sql/mod.rs | 2 + src/sql/sql.rs | 450 +++ src/util/crypto_helper.rs | 143 + src/util/file_util.rs | 350 +++ src/util/logger.rs | 135 + src/util/mod.rs | 3 + 19 files changed, 7090 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/data/communication.rs create mode 100644 src/data/mod.rs create mode 100644 src/main.rs create mode 100644 src/server/api.rs create mode 100644 src/server/mod.rs create mode 100644 src/server/omikron_connection.rs create mode 100644 src/server/omikron_manager.rs create mode 100644 src/server/server.rs create mode 100644 src/server/socket.rs create mode 100644 src/sql/iota_omikron_tracker.rs create mode 100644 src/sql/mod.rs create mode 100644 src/sql/sql.rs create mode 100644 src/util/crypto_helper.rs create mode 100644 src/util/file_util.rs create mode 100644 src/util/logger.rs create mode 100644 src/util/mod.rs diff --git a/.gitignore b/.gitignore index ad67955..e3d273c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,12 @@ target # 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/ +.idea/ + + +.env +/logs + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6bf40cc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4462 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Omega" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "async-tungstenite", + "axum", + "base64 0.22.1", + "bytes", + "color-eyre", + "dashmap", + "dotenv", + "futures", + "futures-util", + "hkdf", + "http-body-util", + "hyper", + "hyper-util", + "json", + "once_cell", + "pnet", + "rand 0.8.5", + "rand_core 0.6.4", + "reqwest", + "rustls 0.23.35", + "rustls-pemfile 2.2.0", + "sha1", + "sha2", + "sqlx", + "sysinfo", + "tokio", + "tokio-rustls", + "tokio-tungstenite", + "tokio-util", + "tower", + "tungstenite", + "uuid", + "walkdir", + "x448", + "zip", +] + +[[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 = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.1", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.3", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.6.0", + "async-lock 3.4.1", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-tls" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e" +dependencies = [ + "futures-core", + "futures-io", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "webpki-roots 0.22.6", +] + +[[package]] +name = "async-tungstenite" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" +dependencies = [ + "async-native-tls", + "async-std", + "async-tls", + "atomic-waker", + "futures-core", + "futures-io", + "futures-task", + "futures-util", + "gio", + "glib", + "log", + "native-tls", + "openssl", + "pin-project-lite", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-native-tls", + "tokio-openssl", + "tokio-rustls", + "tungstenite", + "webpki-roots 1.0.4", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[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.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +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.1", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[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 = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +dependencies = [ + "cc", +] + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[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" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +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 = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +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 = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[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 = "deflate64" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[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 = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[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" +dependencies = [ + "serde", +] + +[[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.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[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", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gio" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeff3dd716d1ba91850b976b76a1c2d28f99ef6c1602cd8fdaa8fab8017fd9c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", +] + +[[package]] +name = "gio-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.61.2", +] + +[[package]] +name = "glib" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gobject-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[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" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "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.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +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 0.23.35", + "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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.1", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "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 = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + +[[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.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +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 = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.6.0", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-rs-sys" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lzma-rust2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +dependencies = [ + "crc", + "sha2", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[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 = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[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 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[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 = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "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.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[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 = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[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 = "pnet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +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.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "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 = "rsa" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[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.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +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.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "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_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "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-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[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" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "async-io 1.13.0", + "async-std", + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.4.1", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.17", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "async-std", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.17", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.17", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.17", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[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.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +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 = "sysinfo" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "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 = "system-deps" +version = "7.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand 2.3.0", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +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-openssl" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" +dependencies = [ + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.35", + "tokio", +] + +[[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", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "hashbrown 0.15.5", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "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.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[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.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.9.2", + "rustls 0.23.35", + "rustls-pki-types", + "sha1", + "thiserror 2.0.17", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[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.2", +] + +[[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.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[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 = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +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" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a1cb5fa --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "Omega" +version = "0.1.0" +edition = "2024" + +[dependencies] +aes-gcm = "*" +async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } +axum = "0.8.7" +base64 = "0.22.1" +bytes = "1.11.0" +color-eyre = "0.6.5" +dashmap = "6.1.0" +dotenv = "0.15.0" +futures = "0.3.31" +futures-util = "0.3.31" +hkdf = "0.12.4" +http-body-util = "0.1.3" +hyper = { version = "1.8.1", features = ["full"] } +hyper-util = "0.1.19" +json = "0.12.4" +once_cell = "1.21.3" +pnet = "0.35.0" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +reqwest = "0.12.28" +rustls = "0.23.35" +rustls-pemfile = "2.2.0" +sha1 = "0.10.6" +sha2 = "0.10.9" +sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } +sysinfo = "0.37.2" +tokio = { version = "*", features = ["full"] } +tokio-rustls = "0.26.4" +tokio-tungstenite = "0.28.0" +tokio-util = { version = "*", features = ["full"] } +tower = "0.5.2" +tungstenite = "0.28.0" +uuid = { version = "1.19.0", features = ["v4"] } +walkdir = "2.5.0" +x448 = "0.6.0" +zip = "6.0.0" diff --git a/src/data/communication.rs b/src/data/communication.rs new file mode 100644 index 0000000..2f8c689 --- /dev/null +++ b/src/data/communication.rs @@ -0,0 +1,454 @@ +use json::number::Number; +use json::{Array, JsonValue, object, parse}; +use std::collections::HashMap; +use std::time::{SystemTime, UNIX_EPOCH}; +use uuid::Uuid; + +#[derive(Eq, Hash, PartialEq, Clone, Debug)] +#[allow(non_camel_case_types, dead_code)] +pub enum DataTypes { + error_type, + accepted_ids, + uuid, + settings, + settings_name, + 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_token, + 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, + + user, +} + +impl DataTypes { + pub fn parse(p0: String) -> DataTypes { + let normalized = p0.to_lowercase().replace('_', ""); + + match normalized.as_str() { + "errortype" => DataTypes::error_type, + "chatpartnerid" => DataTypes::chat_partner_id, + "uuid" => DataTypes::uuid, + "settings" => DataTypes::settings, + "settingsname" => DataTypes::settings_name, + "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, + "calltoken" => DataTypes::call_token, + "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, + + "user" => DataTypes::user, + _ => DataTypes::error_type, // fallback if unknown + } + } +} + +#[derive(PartialEq, Clone, Debug)] +#[allow(non_camel_case_types, dead_code)] +pub enum CommunicationType { + error, + error_invalid_user_id, + error_not_found, + error_no_iota, + error_invalid_challenge, + error_invalid_secret, + error_invalid_private_key, + error_no_user_id, + error_no_call_id, + error_invalid_call_id, + success, + settings_save, + settings_load, + settings_list, + 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_disconnected, + client_closed, + public_key, + private_key, + webrtc_sdp, + webrtc_ice, + start_stream, + end_stream, + watch_stream, + call_token, + call_invite, + end_call, + function, + update, + create_user, +} +impl CommunicationType { + pub fn parse(p0: String) -> CommunicationType { + let normalized = p0.to_lowercase().replace('_', ""); + + match normalized.as_str() { + "watchstream" => CommunicationType::watch_stream, + "calltoken" => CommunicationType::call_token, + "callinvite" => CommunicationType::call_invite, + "endcall" => CommunicationType::end_call, + "function" => CommunicationType::function, + "update" => CommunicationType::update, + "createuser" => CommunicationType::create_user, + "errorinvaliduserid" => CommunicationType::error_invalid_user_id, + "errornotfound" => CommunicationType::error_not_found, + "errornoiota" => CommunicationType::error_no_iota, + "errorinvalidchallenge" => CommunicationType::error_invalid_challenge, + "errorinvalidsecret" => CommunicationType::error_invalid_secret, + "errorinvalidprivatekey" => CommunicationType::error_invalid_private_key, + "errornouserid" => CommunicationType::error_no_user_id, + "errornocallid" => CommunicationType::error_no_call_id, + "errorinvalidcallid" => CommunicationType::error_invalid_call_id, + "success" => CommunicationType::success, + "settingssave" => CommunicationType::settings_save, + "settingsload" => CommunicationType::settings_load, + "settingslist" => CommunicationType::settings_list, + "message" => CommunicationType::message, + "messagesend" => CommunicationType::message_send, + "messagelive" => CommunicationType::message_live, + "messageother_iota" => CommunicationType::message_other_iota, + "messagechunk" => CommunicationType::message_chunk, + "messagesget" => CommunicationType::messages_get, + "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, + "clientdisconnected" => CommunicationType::client_disconnected, + "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, + + _ => CommunicationType::error, + } + } +} + +#[derive(Debug, Clone)] +pub struct CommunicationValue { + pub id: Uuid, + pub comm_type: CommunicationType, + pub sender: i64, + pub receiver: i64, + pub data: HashMap, +} + +#[allow(dead_code)] +impl CommunicationValue { + pub fn new(comm_type: CommunicationType) -> Self { + Self { + id: Uuid::new_v4(), + comm_type, + sender: 0, + receiver: 0, + 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: i64) -> Self { + self.sender = sender; + self + } + pub fn get_sender(&self) -> i64 { + self.sender.clone() + } + pub fn with_receiver(mut self, receiver: i64) -> Self { + self.receiver = receiver; + self + } + pub fn get_receiver(&self) -> i64 { + 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()); + } + if self.sender > 0 && self.receiver > 0 { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + sender: self.sender, + receiver: self.receiver, + data: jdata + } + } else if self.sender > 0 { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + sender: self.sender, + data: jdata + } + } else if self.receiver > 0 { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + receiver: self.receiver, + data: jdata + } + } else { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + data: jdata + } + } + } + + pub fn from_json(json_str: &str) -> Self { + if let Ok(parsed) = parse(json_str) { + let comm_type = CommunicationType::parse(parsed["type"].to_string()); + let mut sender: i64 = 0; + if parsed.has_key("sender") { + sender = parsed["sender"].as_i64().unwrap_or(0); + } + let mut receiver: i64 = 0; + if parsed.has_key("receiver") { + receiver = parsed["receiver"].as_i64().unwrap_or(0); + } + + 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, + } + } else { + Self { + id: Uuid::new_v4(), + comm_type: CommunicationType::error, + sender: 0, + receiver: 0, + data: HashMap::new(), + } + } + } + pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue { + let receiver = original + .get_data(DataTypes::receiver_id) + .unwrap_or(&JsonValue::Number(Number::from(0))) + .as_i64() + .unwrap_or(0); + + let now_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + + let sender = original.get_sender(); + CommunicationValue::new(CommunicationType::message_other_iota) + .with_id(original.get_id()) + .with_receiver(receiver) + .add_data( + DataTypes::receiver_id, + JsonValue::Number(Number::from(receiver)), + ) + .with_sender(sender) + .add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string())) + .add_data( + DataTypes::sender_id, + JsonValue::Number(Number::from(sender)), + ) + .add_data( + DataTypes::content, + JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()), + ) + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..73c2657 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1 @@ +pub mod communication; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..01b51b8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,44 @@ +mod data; +mod server; +mod sql; +mod util; + +use crate::sql::sql::initialize_db; +use crate::sql::sql::print_users; +use crate::util::crypto_helper::load_public_key; +use crate::util::crypto_helper::load_secret_key; +use crate::util::logger::startup; +use dotenv::dotenv; +use once_cell::sync::Lazy; +use std::env; + +static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); +pub fn get_private_key() -> x448::Secret { + load_secret_key(&*PRIVATE_KEY).unwrap() +} +static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); +pub fn get_public_key() -> x448::PublicKey { + load_public_key(&*PUBLIC_KEY).unwrap() +} + +#[tokio::main] +async fn main() { + dotenv().ok(); + startup(); + log!("Started"); + log_in!("Incoming messages"); + log_out!("Outgoing messages"); + log!(" .env"); + if let Err(e) = initialize_db().await { + log!("[FATAL] Database initialization failed: {}", e); + log!( + "[FATAL] Please ensure the database is running and the .env file is configured correctly." + ); + return; + } + let _ = print_users().await; + log!(" DB"); + server::server::start(9187).await; + log!(" Server"); + loop {} +} diff --git a/src/server/api.rs b/src/server/api.rs new file mode 100644 index 0000000..db464a2 --- /dev/null +++ b/src/server/api.rs @@ -0,0 +1,177 @@ +use crate::{get_public_key, log}; +use crate::{ + sql::{ + iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, + sql::{ + change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, + change_keys, change_status, change_username, get_by_id, get_by_username, + get_iota_by_id, get_omikron_by_id, get_random_omikron, get_register_id, + register_complete_iota, register_complete_user, + }, + }, + util::crypto_helper::public_key_to_base64, +}; +use axum::http::HeaderValue; +use http_body_util::Full; +use hyper::body::Bytes; +use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; +use json::JsonValue; + +pub async fn handle( + path: &str, + headers: HeaderMap, + body_string: Option, +) -> HttpResponse> { + let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); + + let body: Option = if body_string.is_some() { + if let Ok(body_json) = json::parse(&body_string.unwrap()) { + Some(body_json) + } else { + None + } + } else { + None + }; + // api/ + // get/ + // omikron/ + // id/ + // register/ + // innit/ + // complete + log!("{}", path); + log!("{:?} .len = {}", path_parts, path_parts.len()); + let (status, content, body_text) = if path_parts.len() >= 2 { + match path_parts[1] { + "get" => match path_parts[2] { + // api/get/omikron -> any omikron + // api/get/omikron/ -> omikron for id (user / iota / omikron) + "omikron" => { + if path_parts.len() == 3 { + if let Ok((id, public_key, ip_address)) = get_random_omikron().await { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else if path_parts.len() == 4 { + let id = path_parts[3].parse::().unwrap_or(0); + if id == 0 { + not_found() + } else if let Ok((omikron_id, public_key, ip_address)) = + get_omikron_by_id(id).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else if let Some(omikron_id) = get_omikron_for_iota(id).await { + if let Ok((omikron_id, public_key, ip_address)) = + get_omikron_by_id(omikron_id).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_id(id).await + { + if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { + if let Ok((omikron_id, public_key, ip_address)) = + get_omikron_by_id(omikron_id).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else { + not_found() + } + } else { + not_found() + } + } else { + bad_request() + } + } + // get/id/ + "id" => { + let username = path_parts[3]; + bad_request() + } + "public_key" => ( + StatusCode::OK, + "application/json", + public_key_to_base64(&get_public_key()), + ), + _ => { + let id = path_parts[2]; + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else { + bad_request() + } + } + }, + _ => not_found(), + } + } else { + not_found() + }; + let body = Full::new(Bytes::from(body_text.to_string())); + HttpResponse::builder().status(status).body(body).unwrap() +} +pub fn bad_request() -> (StatusCode, &'static str, String) { + ( + StatusCode::BAD_REQUEST, + "text/text", + "400 Bad Request".to_string(), + ) +} +pub fn unauthorized() -> (StatusCode, &'static str, String) { + ( + StatusCode::UNAUTHORIZED, + "text/text", + "401 Unauthorized".to_string(), + ) +} +pub fn forbidden() -> (StatusCode, &'static str, String) { + ( + StatusCode::FORBIDDEN, + "text/text", + "403 Forbidden".to_string(), + ) +} +pub fn not_found() -> (StatusCode, &'static str, String) { + ( + StatusCode::NOT_FOUND, + "text/text", + "404 Not Found".to_string(), + ) +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..39df0d8 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,5 @@ +pub mod api; +pub mod omikron_connection; +pub mod omikron_manager; +pub mod server; +pub mod socket; diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs new file mode 100644 index 0000000..5aafd16 --- /dev/null +++ b/src/server/omikron_connection.rs @@ -0,0 +1,258 @@ +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::get_public_key; +use crate::sql::sql::get_omikron_by_id; +use crate::util::crypto_helper::encrypt; +use crate::{get_private_key, log_in_from, log_out_from}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use dashmap::DashMap; +use futures::SinkExt; +use futures::stream::SplitSink; +use futures::stream::SplitStream; +use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; +use json::JsonValue; +use rand::Rng; +use rand::distributions::Alphanumeric; +use std::sync::Arc; +use tokio::sync::RwLock; +use tokio_tungstenite::WebSocketStream; +use tungstenite::Message; +use tungstenite::Utf8Bytes; +use uuid::Uuid; +use x448::PublicKey; + +pub struct OmikronConnection { + pub sender: Arc>, Message>>>, + pub receiver: Arc>>>>, + pub omikron_id: Arc>, + pub pub_key: Arc>>>, + identified: Arc>, + challenged: Arc>, + challenge: Arc>, + pub ping: Arc>, + waiting_tasks: DashMap< + Uuid, + Box, CommunicationValue) -> bool + Send + Sync>, + >, +} + +impl OmikronConnection { + pub fn new( + sender: SplitSink>, Message>, + receiver: SplitStream>>, + ) -> Arc { + Arc::new(Self { + sender: Arc::new(RwLock::new(sender)), + receiver: Arc::new(RwLock::new(receiver)), + omikron_id: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + identified: Arc::new(RwLock::new(false)), + challenged: Arc::new(RwLock::new(false)), + challenge: Arc::new(RwLock::new(String::new())), + ping: Arc::new(RwLock::new(-1)), + waiting_tasks: DashMap::new(), + }) + } + pub async fn send_message(&self, message: &CommunicationValue) { + let mut sender = self.sender.write().await; + let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string())); + log_out_from!(*self.omikron_id.read().await, "{}", message_text); + sender.send(message_text).await.unwrap(); + } + pub async fn get_user_id(&self) -> i64 { + *self.omikron_id.read().await + } + pub async fn is_identified(&self) -> bool { + *self.identified.read().await && *self.challenged.read().await + } + pub async fn get_public_key(&self) -> PublicKey { + PublicKey::from_bytes(self.pub_key.read().await.as_ref().unwrap()).unwrap() + } + + pub async fn handle_message(self: Arc, message: String) { + let cv = CommunicationValue::from_json(&message); + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + + log_in_from!(*self.omikron_id.read().await, "{}", message); + + if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { + let omikron_id = cv + .get_data(DataTypes::omikron) + .unwrap_or(&JsonValue::Null) + .as_i64() + .unwrap_or(0); + if let Ok((_, public_key, _)) = get_omikron_by_id(omikron_id).await { + // Generate Challenge, encrypt it and send it to the omikron + *self.omikron_id.write().await = omikron_id; + *self.identified.write().await = true; + + let challenge_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge_str.clone(); + + let user_public_key_bytes = match STANDARD.decode(&public_key) { + Ok(bytes) => bytes, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_user_id, + ) + .await; + return; + } + }; + *self.pub_key.write().await = Some(user_public_key_bytes.clone()); + + let omikron_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) + { + Some(key) => key, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_user_id, + ) + .await; + return; + } + }; + + let encrypted_challenge = + encrypt(get_private_key(), omikron_pub_key, &challenge_str) + .unwrap_or("".to_string()); + + let response = CommunicationValue::new(CommunicationType::challenge) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), + ) + .add_data_str(DataTypes::challenge, encrypted_challenge) + .with_id(cv.get_id()); + + self.send_message(&response).await; + // prepare Challenge Response handling + self.waiting_tasks.insert( + cv.get_id(), + Box::new( + |selfc: Arc, cv: CommunicationValue| -> bool { + tokio::spawn(async move { + let client_challenge_response_b64 = + match cv.get_data(DataTypes::challenge) { + Some(data) => data.to_string(), + None => { + selfc + .send_error_response( + &cv.get_id(), + CommunicationType::error, + ) + .await; + return; + } + }; + + let challenge_response_bytes = + match STANDARD.decode(&client_challenge_response_b64) { + Ok(bytes) => bytes, + Err(_) => { + selfc + .send_error_response( + &cv.get_id(), + CommunicationType::error, + ) + .await; + return; + } + }; + + if challenge_response_bytes.len() < 12 { + selfc + .send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + + let client_response = cv + .get_data(DataTypes::challenge) + .unwrap_or(&JsonValue::Null) + .as_str() + .unwrap_or(""); + + let expected_challenge = selfc.challenge.read().await.clone(); + + if client_response != expected_challenge { + selfc + .send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_challenge, + ) + .await; + selfc.close().await; + return; + } + + *selfc.challenged.write().await = true; + + let response = CommunicationValue::new( + CommunicationType::identification_response, + ) + .with_id(cv.get_id()); + + selfc.send_message(&response).await; + return; + }); + return true; + }, + ), + ); + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + .await; + } + return; + } + + if self.waiting_tasks.contains_key(&cv.get_id()) { + let (_, task) = self.waiting_tasks.remove(&cv.get_id()).unwrap(); + let _ = task(self.clone(), cv.clone()); + } + + if !self.is_identified().await { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + .await; + return; + } + } + + 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; + } + pub async fn close(&self) { + let mut sender = self.sender.write().await; + let _ = sender.close().await; + } + pub async fn handle_close(self: Arc) { + if self.is_identified().await { + if self.get_user_id().await != 0 {} + } + } + + async fn handle_ping(&self, cv: CommunicationValue) { + 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; + } + } + + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + + self.send_message(&response).await; + } +} diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/server/server.rs b/src/server/server.rs new file mode 100644 index 0000000..7dc19ab --- /dev/null +++ b/src/server/server.rs @@ -0,0 +1,475 @@ +use crate::log; +use crate::server::api; +use crate::server::omikron_connection::OmikronConnection; +use crate::util::file_util::load_file_buf; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use bytes::Bytes; +use futures::StreamExt; +use futures_util::TryFutureExt; +use http_body_util::BodyExt; +use http_body_util::Full; +use hyper::{Method, StatusCode}; +use hyper::{ + Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade, +}; +use hyper_util::rt::tokio::TokioIo; +use hyper_util::service::TowerToHyperService; +use pnet::datalink::NetworkInterface; +use rustls::ServerConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use sha1::{Digest, Sha1}; +use std::error::Error; +use std::io::ErrorKind; +use std::io::{self, BufReader}; +use std::net::SocketAddr; +use std::result::Result::Ok; +use std::sync::Arc; +use std::{future::Future, pin::Pin, time::Duration}; +use tokio::net::TcpListener; +use tokio::sync::broadcast; +use tokio_rustls::TlsAcceptor; +use tokio_tungstenite::{WebSocketStream, accept_async}; +use tower::Service; +use tungstenite::Message; +#[derive(Clone)] +struct HttpService { + peer_addr: SocketAddr, +} + +impl Service> for HttpService { + type Response = HttpResponse>; + type Error = io::Error; + type Future = Pin> + Send>>; + + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(std::io::Result::Ok(())) + } + + fn call(&mut self, req: HttpRequest) -> Self::Future { + let _peer_ip = self.peer_addr.ip(); + + let (parts, body) = req.into_parts(); + + let method = parts.method.clone(); + let path = parts.uri.path().to_string(); + let headers = parts.headers.clone(); + + let fut = async move { + let is_websocket_upgrade = path == "/ws/omikron" + && method == Method::GET + && headers + .get("connection") + .map(|v| { + v.to_str() + .unwrap_or("") + .split(',') + .any(|s| s.trim().eq_ignore_ascii_case("upgrade")) + }) + .unwrap_or(false) + && headers + .get("upgrade") + .map(|v| v.to_str().unwrap_or("").eq_ignore_ascii_case("websocket")) + .unwrap_or(false); + + if is_websocket_upgrade { + log!("Attempting WebSocket upgrade on {}", path); + + if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { + let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string(); + let sec_websocket_accept = calculate_accept_key(&sec_websocket_key); + + let response = HttpResponse::builder() + .status(StatusCode::SWITCHING_PROTOCOLS) + .header("Upgrade", "websocket") + .header("Connection", "Upgrade") + .header("Sec-WebSocket-Accept", sec_websocket_accept) + .body(Full::new(Bytes::from(""))) + .unwrap(); + let req_for_upgrade = HttpRequest::from_parts(parts, body); + let upgrades = upgrade::on(req_for_upgrade); + log!("Handling WebSocket upgrade"); + + // Spawn upgrade handling to avoid blocking the service call + tokio::spawn(async move { + match upgrades.await { + Ok(upgraded_stream) => { + log!("Valid WebSocket upgrade"); + let raw_stream = TokioIo::new(upgraded_stream); + + let ws_stream = WebSocketStream::from_raw_socket( + raw_stream, + tungstenite::protocol::Role::Server, + None, + ) + .await; + log!( + "WebSocket handshake successful, handling connection for Omikron" + ); + + // Split stream for OmikronConnection + let (writer, reader) = ws_stream.split(); + + // INTEGRATION START + // Erstelle die Connection und starte den Handler + let connection = OmikronConnection::new(writer, reader); + + // Handler in separatem Task starten + start_omikron_handler(connection).await; + // INTEGRATION END + } + Err(e) => { + log!("WebSocket upgrade failed after response: {:?}", e); + } + } + }); + + log!("Handled WebSocket connection initiation"); + Ok(response) + } else { + log!("No Sec-WebSocket-Key found in request headers"); + let response = HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) + .unwrap(); + Ok(response) + } + } else if path.starts_with("/api") { + let whole_body = match body.collect().await { + Ok(collected) => collected, + Err(e) => { + log!("Error collecting body: {}", e); + return Ok(HttpResponse::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Full::new(Bytes::from(format!( + "Failed to read body: {}", + e + )))) + .unwrap()); + } + }; + let bytes = whole_body.to_bytes(); + + let body_string: Option = match String::from_utf8(bytes.to_vec()) { + Ok(s) => Some(s), + Err(_) => None, + }; + + Ok(api::handle(&path, headers.clone(), body_string).await) + } else { + let response = HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::new(Bytes::from("No path provided"))) + .unwrap(); + Ok(response) + } + }; + + Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { + io::Error::new( + io::ErrorKind::Other, + format!("Error in request handling: {}", err), + ) + })) + } +} + +async fn run_http_server(port: u16) -> bool { + let ip = "0.0.0.0".to_string(); + let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener { + log!("Failed to bind to port {}: {:?}", port, e); + return false; + } + let listener = listener.unwrap(); + log!( + "Standard Server listening for HTTP and WS on {}:{}", + ip, + port + ); + + // Create a broadcast channel for graceful shutdown signal + let (shutdown_tx, _) = broadcast::channel::<()>(1); + + tokio::spawn(async move { + loop { + tokio::select! { + + accepted = listener.accept() => { + match accepted { + std::result::Result::Ok((stream, addr)) => { + let service = HttpService { peer_addr: addr }; + let io = TokioIo::new(stream); + + // Subscribe to the shutdown signal for this specific connection + let mut rx = shutdown_tx.subscribe(); + + tokio::spawn(async move { + // Prepare the connection future + let conn = http1::Builder::new() + .preserve_header_case(true) + .title_case_headers(true) + .serve_connection(io, TowerToHyperService::new(service)) + .with_upgrades(); + + // Wait for either the connection to finish naturally OR the shutdown signal + tokio::select! { + res = conn => { + if let Err(err) = res { + if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { + if io_err.kind() != io::ErrorKind::ConnectionReset + && io_err.kind() != io::ErrorKind::BrokenPipe + { + log!("Error serving connection: {:?}", err); + } + } + } + } + _ = rx.recv() => { + // Shutdown signal received. + // Dropping the 'conn' future here closes the socket immediately. + } + } + }); + } + Err(e) => { + log!("Error accepting connection: {:?}", e); + tokio::time::sleep(Duration::from_millis(500)).await; + } + } + } + } + } + }); + + true +} + +/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config. +async fn run_tls_server(port: u16, tls_config: Arc) -> bool { + let mut ip = "0.0.0.0".to_string(); + for iface in pnet::datalink::interfaces() { + let iface: NetworkInterface = iface; + let ipsv = format!("{}", iface.ips[0]); + let ips: &str = ipsv.split('/').next().unwrap(); + log!("{}", ips.to_string()); + if format!("{}", ips).starts_with("10.") { + ip = ips.to_string(); + } + } + + let acceptor = TlsAcceptor::from(tls_config); + + let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener { + log!("Failed to bind to port {}: {:?}", port, e); + return false; + } + let listener = listener.unwrap(); + log!( + "Encrypted Server listening for HTTPS and WSS on {}:{}", + ip, + port + ); + + // Create a broadcast channel for graceful shutdown signal + let (shutdown_tx, _) = broadcast::channel::<()>(1); + + tokio::spawn(async move { + loop { + tokio::select! { + // Monitor for shutdown signal + _ = async { + loop { + tokio::time::sleep(Duration::from_millis(100)).await; + } + } => { + log!("Encrypted Server received shutdown signal."); + // Send kill signal to all active connection tasks + let _ = shutdown_tx.send(()); + break; + } + + // Accept new connections + accepted = listener.accept() => { + match accepted { + std::result::Result::Ok((stream, addr)) => { + let service = HttpService { peer_addr: addr }; + let acceptor = acceptor.clone(); + + // Subscribe to the shutdown signal for this specific connection + let mut rx = shutdown_tx.subscribe(); + + tokio::spawn(async move { + // Perform TLS handshake + let tls_stream = match acceptor.accept(stream).await { + Ok(s) => s, + Err(e) => { + if e.kind() != io::ErrorKind::Interrupted { + log!("TLS Handshake error: {:?}", e); + } + return; + } + }; + let io = TokioIo::new(tls_stream); + + // Prepare connection future + let conn = http1::Builder::new() + .preserve_header_case(true) + .title_case_headers(true) + .serve_connection(io, TowerToHyperService::new(service)) + .with_upgrades(); + + // Wait for either the connection to finish naturally OR the shutdown signal + tokio::select! { + res = conn => { + if let Err(err) = res { + if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { + if io_err.kind() != io::ErrorKind::ConnectionReset + && io_err.kind() != io::ErrorKind::BrokenPipe + { + log!("Error serving connection: {:?}", err); + } + } + } + } + _ = rx.recv() => { + // Shutdown signal received. + // Dropping the 'conn' future here closes the socket immediately. + } + } + }); + } + Err(e) => { + log!("Error accepting connection: {:?}", e); + tokio::time::sleep(Duration::from_millis(500)).await; + } + } + } + } + } + + log!("Encrypted Server shutdown complete."); + }); + true +} + +pub async fn start(port: u16) -> bool { + let tls_result = load_tls_config(); + + match tls_result { + Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, + Ok(None) => run_http_server(port).await, + Err(e) => { + log!("Fatal error during TLS config load: {}", e); + false + } + } +} +fn calculate_accept_key(key: &str) -> String { + let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + let mut sha1 = Sha1::new(); + sha1.update(key.as_bytes()); + sha1.update(websocket_guid.as_bytes()); + let result = sha1.finalize(); + STANDARD.encode(result) // Base64 encode the result +} + +/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. +fn load_tls_config() -> Result>, Box> { + let cert_file_res = load_file_buf("certs", "cert.pem"); + let key_file_res = load_file_buf("certs", "cert.key"); + + // Check if certificate files are present. If not, return None. + let cert_file_buf = match cert_file_res { + Ok(b) => b, + Err(e) if e.kind() == ErrorKind::NotFound => { + log!("TLS certificate 'certs/cert.pem' not found."); + return Ok(None); + } + Err(e) => return Err(e.into()), // Other IO error + }; + + let key_file_buf = match key_file_res { + Ok(b) => b, + Err(e) if e.kind() == ErrorKind::NotFound => { + log!("TLS key 'certs/cert.key' not found."); + return Ok(None); + } + Err(e) => return Err(e.into()), // Other IO error + }; + + // Continue with configuration if both files were found + let mut cert_reader = BufReader::new(cert_file_buf); + let cert_ders = rustls_pemfile::certs(&mut cert_reader) + .collect::, io::Error>>()?; + + // PKCS8 + let mut key_reader = BufReader::new(key_file_buf); + let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) + .map(|r| r.map(Into::into)) // Explicit conversion + .collect::, io::Error>>()?; + + if key_ders.is_empty() { + // RSA + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) + .map(|r| r.map(Into::into)) + .collect::, io::Error>>()?; + } + + if key_ders.is_empty() { + // EC + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) + .map(|r| r.map(Into::into)) + .collect::, io::Error>>()?; + } + + if key_ders.is_empty() { + return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); + } + + let config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_ders, key_ders.remove(0)) + .map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?; + + Ok(Some(Arc::new(config))) +} +// In deiner Server-Logik, wo OmikronConnection initialisiert wird: + +pub async fn start_omikron_handler(connection: Arc) { + loop { + let msg = match { + let mut receiver = connection.receiver.write().await; + receiver.next().await + } { + Some(Ok(msg)) => msg, + Some(Err(e)) => { + log!("WS Error: {}", e); + break; + } + None => break, // Stream ended + }; + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + // Other message types like Binary, Ping, Pong are ignored. + _ => {} + } + } + connection.handle_close().await; +} diff --git a/src/server/socket.rs b/src/server/socket.rs new file mode 100644 index 0000000..2ff5966 --- /dev/null +++ b/src/server/socket.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use futures::StreamExt; +use futures::stream::SplitSink; +use futures::stream::SplitStream; +use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; +use tungstenite::Message; + +use crate::log; +use crate::server::omikron_connection::OmikronConnection; + +pub fn handle( + path: String, + writer: SplitSink>, Message>, + reader: SplitStream>>, +) { + log!("handling"); + tokio::spawn(async move { + if path.starts_with("/ws/phi/") { + } else if path.starts_with("/ws/omikron/") { + let community_conn: Arc = + Arc::from(OmikronConnection::new(writer, reader)); + loop { + let msg_result: Option> = { + let mut session_lock = community_conn.receiver.write().await; + session_lock.next().await + }; + + match msg_result { + Some(Ok(msg)) => { + if msg.is_text() { + let text = msg.into_text().unwrap(); + community_conn + .clone() + .handle_message(text.to_string()) + .await; + } else if msg.is_close() { + log!("Closing: {}", msg); + community_conn.handle_close().await; + return; + } + } + Some(Err(e)) => { + log!("Closing ERR: {}", e); + community_conn.handle_close().await; + return; + } + None => { + log!("Closed Session me!"); + community_conn.handle_close().await; + return; + } + } + } + } + }); +} diff --git a/src/sql/iota_omikron_tracker.rs b/src/sql/iota_omikron_tracker.rs new file mode 100644 index 0000000..7d768ca --- /dev/null +++ b/src/sql/iota_omikron_tracker.rs @@ -0,0 +1,22 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +static IOTA_OMIKRON_MAP: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn track_iota_omikron(iota: i64, omikron: i64) { + let mut c = IOTA_OMIKRON_MAP.write().await; + c.insert(iota, omikron); +} + +pub async fn get_omikron_for_iota(iota: i64) -> Option { + let c = IOTA_OMIKRON_MAP.read().await; + c.get(&iota).cloned() +} + +pub async fn untrack_iota(iota: i64) { + let mut c = IOTA_OMIKRON_MAP.write().await; + c.remove(&iota); +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs new file mode 100644 index 0000000..9ef349a --- /dev/null +++ b/src/sql/mod.rs @@ -0,0 +1,2 @@ +pub mod iota_omikron_tracker; +pub mod sql; diff --git a/src/sql/sql.rs b/src/sql/sql.rs new file mode 100644 index 0000000..de749fa --- /dev/null +++ b/src/sql/sql.rs @@ -0,0 +1,450 @@ +use crate::log; +use once_cell::sync::Lazy; +use sqlx::{MySql, Pool, mysql::MySqlPoolOptions}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{ + env, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; +use tokio::sync::RwLock; + +/* +use crate::sql::{ + iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, + sql::{ + change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, + change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, + get_register_id, register_complete_iota, register_complete_user, + }, +}; + */ + +static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); + +pub async fn connect() -> Result, sqlx::Error> { + let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); + let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); + let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); + + MySqlPoolOptions::new() + .max_connections(5) + .connect(&format!( + "mysql://{}:{}@127.0.0.1:3306/{}", + user, passwd, table + )) + .await +} +// Omega +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +pub async fn initialize_db() -> Result<(), sqlx::Error> { + let pool = connect().await?; + let mut db_lock = SQL_DB.write().await; + // create tables + // with indexes + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + users ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, + display VARCHAR(15) COLLATE utf8mb4_bin, + status VARCHAR(15) COLLATE utf8mb4_bin, + about VARCHAR(200) COLLATE utf8mb4_bin, + avatar MEDIUMBLOB, + sub_level INT(11) NOT NULL DEFAULT 0, + sub_end BIGINT(20) NOT NULL, + public_key TEXT NOT NULL COLLATE utf8mb4_bin, + private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin, + iota_id BIGINT UNSIGNED NOT NULL, + token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + iotas ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + public_key TEXT NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + omikrons ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + is_active INT(1) NOT NULL DEFAULT 0, + public_key TEXT NOT NULL COLLATE utf8mb4_bin, + location TEXT NOT NULL COLLATE utf8mb4_bin, + ip_address TEXT NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + + *db_lock = Some(pool); + Ok(()) +} + +// ========================================================================================== +// REGISTER +// ========================================================================================== +pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); +pub static CURRENT_REGISTER_PROCESS: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); + +pub async fn get_register_id() -> u64 { + let mut current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + loop { + let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); + + if current_locked < current_time { + let result = CURRENT_MILLI_USED.compare_exchange( + current_locked, // expected value + current_time, // new value + Ordering::SeqCst, // acquire/release ordering + Ordering::SeqCst, // failure ordering + ); + + match result { + Ok(_) => { + CURRENT_REGISTER_PROCESS.write().await.push(current_time); + return current_time; + } + Err(_) => { + continue; + } + } + } else { + current_time = current_locked + 1; + } + } +} + +// ========================================================================================== +// USERS +// ========================================================================================== + +pub async fn get_by_username( + username: &str, +) -> Result< + ( + i64, + i64, + String, + String, + String, + String, + String, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( + "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", + ) + .bind(username) + .fetch_optional(pool) + .await?.ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn get_by_id( + id: i64, +) -> Result< + ( + i64, + i64, + String, + String, + String, + String, + String, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( + "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", + ) + .bind(id) + .fetch_optional(pool) + .await?.ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET username = ? WHERE id = ?") + .bind(new_username) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_display_name(id: i64, new_display_name: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET display_name = ? WHERE id = ?") + .bind(new_display_name) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") + .bind(new_avatar) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET about = ? WHERE id = ?") + .bind(new_about) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET status = ? WHERE id = ?") + .bind(new_status) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") + .bind(new_iota_id) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_keys( + id: i64, + new_public_key: String, + new_private_key_hash: String, +) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn register_complete_user( + id: i64, + username: String, + public_key: String, + private_key_hash: String, + iota_id: i64, + reset_token: String, +) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, reset_token) VALUES (?, ?, ?, ?, ?, ?)") + .bind(id) + .bind(username) + .bind(public_key) + .bind(private_key_hash) + .bind(iota_id) + .bind(reset_token) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn print_users() -> Result<(), Box> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + log!("Printing users..."); + for (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) + in sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( + "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users", + ) + .fetch_all(pool) + .await? + .iter() + { + log!( + "User: {:?}", + (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) + ); + } + + Ok(()) +} + +// ========================================================================================== +// IOTA +// ========================================================================================== +pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") + .bind(id) + .bind(public_key) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM users WHERE id = ?") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, String)>("SELECT id, public_key FROM iotas WHERE id = ?") + .bind(id) + .fetch_optional(pool) + .await? + .ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + .bind(new_key) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM iotas WHERE id = ?") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +// ========================================================================================== +// OMIKRONS +// ========================================================================================== + +pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + let a = sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") + .fetch_optional(pool) + .await? + .ok_or_else(|| sqlx::Error::RowNotFound); + // this log isn't printing + log!("Random Omikron: {:?}", a); + a +} + +pub async fn get_omikron_by_id(id: i64) -> Result<(i64, String, String), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE id = ?") + .bind(id) + .fetch_optional(pool) + .await? + .ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + let active = if active { 1 } else { 0 }; + + sqlx::query("UPDATE omikrons SET active = ? WHERE id = ?") + .bind(active) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs new file mode 100644 index 0000000..bb086b8 --- /dev/null +++ b/src/util/crypto_helper.rs @@ -0,0 +1,143 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, OsRng}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use rand_core::RngCore; +use sha2::{Digest, Sha256}; +use x448::{PublicKey, Secret, SharedSecret}; + +/// Errors for crypto operations +#[derive(Debug)] +pub enum CryptoError { + Base64Decode(base64::DecodeError), + InvalidKey, + AgreementError, + EncryptionError(aes_gcm::Error), + DecryptionError(aes_gcm::Error), +} + +impl From for CryptoError { + fn from(err: base64::DecodeError) -> Self { + CryptoError::Base64Decode(err) + } +} + +pub struct KeyPair { + pub secret: Secret, + pub public: PublicKey, +} + +pub fn generate_keypair() -> KeyPair { + let mut buf = [0u8; 56]; + let mut rng = OsRng; + rng.fill_bytes(&mut buf); + let secret = Secret::from_bytes(&buf).unwrap(); + let public = PublicKey::from(&secret); + KeyPair { secret, public } +} + +pub fn public_key_to_base64(pubkey: &PublicKey) -> String { + STANDARD.encode(pubkey.as_bytes().as_ref()) +} + +pub fn secret_key_to_base64(secret: &Secret) -> String { + STANDARD.encode(secret.as_bytes().as_ref()) +} + +pub fn load_public_key(base64_pub: &str) -> Option { + let bytes = STANDARD.decode(base64_pub).unwrap(); + PublicKey::from_bytes(&bytes) +} + +pub fn load_secret_key(base64_secret: &str) -> Option { + let bytes = STANDARD.decode(base64_secret).unwrap(); + Secret::from_bytes(&bytes) +} + +fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(shared.as_bytes()); + let result = hasher.finalize(); + let mut key = [0u8; 32]; + key.copy_from_slice(&result[..32]); + key +} + +pub fn encrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + plaintext: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + encrypt(secret, peer_pub, plaintext) +} +pub fn encrypt( + secret: Secret, + peer_pub: PublicKey, + plaintext: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + let mut nonce_bytes = [0u8; 12]; + OsRng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + let ciphertext = cipher + .encrypt(nonce, plaintext.as_bytes()) + .map_err(CryptoError::EncryptionError)?; + // prefix nonce to ciphertext + let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); + out.extend_from_slice(&nonce_bytes); + out.extend_from_slice(&ciphertext); + Ok(STANDARD.encode(&out)) +} + +pub fn decrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + encrypted_base64: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + decrypt(secret, peer_pub, encrypted_base64) +} +pub fn decrypt( + secret: Secret, + peer_pub: PublicKey, + encrypted_base64: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + + let encrypted = STANDARD.decode(encrypted_base64)?; + if encrypted.len() < 12 { + return Err(CryptoError::DecryptionError(aes_gcm::Error)); + } + let nonce_bytes = &encrypted[..12]; + let ciphertext = &encrypted[12..]; + let nonce = Nonce::from_slice(nonce_bytes); + let plaintext_bytes = cipher + .decrypt(nonce, ciphertext) + .map_err(CryptoError::DecryptionError)?; + let plaintext = String::from_utf8(plaintext_bytes) + .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; + Ok(plaintext) +} + +pub fn hash_it(input: &str) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(input.as_bytes()); + hasher.finalize().to_vec() +} + +pub fn hex_hash(input: &str) -> String { + let digest = hash_it(input); + digest.iter().map(|b| format!("{:02x}", b)).collect() +} diff --git a/src/util/file_util.rs b/src/util/file_util.rs new file mode 100644 index 0000000..383ba3b --- /dev/null +++ b/src/util/file_util.rs @@ -0,0 +1,350 @@ +use crate::log; +use std::ffi::OsStr; +use std::fs::{self, File}; +use std::io::{self, BufReader, Read}; +use std::path::{Path, PathBuf}; +use sysinfo::System; +use uuid::Uuid; +use walkdir::WalkDir; +use zip::ZipArchive; + +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) { + log!( + "[IMPORTANT] Couldn't delete directory {}: {}", + directory.display(), + e + ); + return false; + } + true +} + +pub fn load_file_buf(path: &str, name: &str) -> io::Result> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + // Ensure the directory exists, create if necessary + if !dir.exists() { + if let Err(_) = fs::create_dir_all(&dir) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Directory creation failed", + )); + } + } + + // Create the file if it doesn't exist + if !file_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "File creation failed", + )); + } + + // Open the file and return a BufReader for efficient reading + let file = File::open(&file_path)?; + Ok(BufReader::new(file)) +} +pub fn has_file(path: &str, name: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + return false; + } + + if !file_path.exists() { + return false; + } + + true +} +pub fn has_dir(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + + if !dir.exists() { + return false; + } + + true +} + +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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return String::new(); + } + return String::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log!("[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 load_file_vec(path: &str, name: &str) -> Vec { + 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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return Vec::new(); + } + return Vec::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log!("[IMPORTANT] Couldn't create file: {}", e); + } + return Vec::new(); + } + + let mut content = Vec::new(); + if let Ok(mut f) = File::open(&file_path) { + let _ = f.read_to_end(&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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return; + } + } + + if let Err(e) = fs::write(&file_path, value) { + log!( + "[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 used_dir_space(path: &str) -> u64 { + get_directory_size(&PathBuf::from(format!("{}/{}", get_directory(), path))) +} + +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)) +} + +// Helper to download the zip file content to a file on disk +async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { + let response = reqwest::get(url).await?; + + // Check for successful response status + if !response.status().is_success() { + return Err(format!("Failed to download file: Status {}", response.status()).into()); + } + + let mut zip_file = File::create(as_name)?; + let body = response.bytes().await?; + io::copy(&mut &*body, &mut zip_file)?; + + Ok(()) +} + +fn extract_zip_contents_to_folder( + zip_path: &Path, + target_dir: &Path, +) -> Result<(), Box> { + let file = File::open(zip_path)?; + let mut archive = ZipArchive::new(file)?; + + let staging_dir = target_dir.with_extension("staging"); + + let _ = fs::remove_dir_all(&staging_dir); + fs::create_dir_all(&staging_dir)?; + + let mut first_item_name: Option = None; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let entry_path = staging_dir.join(file.sanitized_name()); + + if i == 0 { + if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { + first_item_name = Some(file.sanitized_name()); + } + } + + if file.name().ends_with('/') { + fs::create_dir_all(&entry_path)?; + } else { + if let Some(parent) = entry_path.parent() { + fs::create_dir_all(parent)?; + } + let mut out_file = File::create(entry_path)?; + io::copy(&mut file, &mut out_file)?; + } + } + + if let Some(root_path) = first_item_name { + let root_dir = staging_dir.join(&root_path); + + if root_dir.is_dir() { + let root_contents_count = fs::read_dir(&staging_dir)?.count(); + + if root_contents_count == 1 + || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) + { + let _ = fs::remove_dir_all(target_dir); + fs::create_dir_all(target_dir)?; + + for entry in fs::read_dir(root_dir)? { + let entry = entry?; + let src = entry.path(); + let dest = target_dir.join(entry.file_name()); + + if let Err(_) = fs::rename(&src, &dest) { + if src.is_file() { + fs::copy(&src, &dest)?; + } else { + if entry.path().is_dir() { + fs::rename(&src, &dest)?; + } + } + } + } + + let _ = fs::remove_dir_all(&staging_dir); + return Ok(()); + } + } + } + + log!("Extracting directly (no single root folder detected)."); + let _ = fs::remove_dir_all(target_dir); + fs::rename(&staging_dir, target_dir)?; + + Ok(()) +} + +pub async fn download_and_extract_zip(url: &str, as_name: &str) { + let base_dir = PathBuf::from(get_directory()); + let zip_filename = format!("{}.zip", Uuid::new_v4()); // Use a unique name for the downloaded ZIP file + let zip_path = base_dir.join(&zip_filename); + let target_dir = base_dir.join(as_name); + + // Step 1: Download the ZIP file + if let Err(e) = download_zip(url, &zip_path).await { + log!("Error downloading file: {}", e); + return; + } + + // Step 2: Extract and flatten the ZIP file contents into the target directory + if let Err(e) = extract_zip_contents_to_folder(&zip_path, &target_dir) { + log!("Error extracting ZIP file contents: {}", e); + } + + // Step 3: Clean up the downloaded ZIP file + if let Err(e) = fs::remove_file(&zip_path) { + log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs new file mode 100644 index 0000000..47173e9 --- /dev/null +++ b/src/util/logger.rs @@ -0,0 +1,135 @@ +use std::{ + fs::{self, OpenOptions}, + io::Write, + path::Path, + sync::{OnceLock, mpsc}, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; +static LOGGER: OnceLock> = OnceLock::new(); + +struct LogMessage { + timestamp_ms: u128, + sender: Option, + message: String, +} + +/// Initialize logger (call once) +pub fn startup() { + let (tx, rx) = mpsc::channel::(); + + LOGGER.set(tx).expect("Logger already initialized"); + + thread::spawn(move || { + // Prepare log directory + let log_dir = Path::new("logs"); + fs::create_dir_all(log_dir).expect("Failed to create log directory"); + + let start_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let path = log_dir.join(format!("log_{}.txt", start_ts)); + + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .expect("Failed to open log file"); + + // Dedicated logging loop + for msg in rx { + let timestamp_box = fixed_box(&msg.timestamp_ms.to_string(), 13); + + let sender_box = match msg.sender { + Some(id) => fixed_box(&format!("{}", id), 19), + None => fixed_box("", 19), + }; + + let line = format!("{} {} {}", timestamp_box, sender_box, msg.message); + + println!("{}", line); + let _ = writeln!(file, "{}", line); + } + }); +} +fn fixed_box(content: &str, width: usize) -> String { + let s = content.chars().take(width).collect::(); + let len = s.chars().count(); + if len < width { + let mut a = " ".repeat(width - len); + a.push_str(&s); + format!("[{}]", a) + } else { + s + } +} +/// Internal function (sync + async safe) +pub fn log_internal(sender: Option, message: String) { + if let Some(tx) = LOGGER.get() { + let _ = tx.send(LogMessage { + timestamp_ms: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + sender, + message, + }); + } else { + println!("{}", message); + } +} + +#[macro_export] +macro_rules! log { + ($($arg:tt)*) => { + $crate::util::logger::log_internal(None, format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! log_from { + ($sender:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), format!($($arg)*)) + }; +} +#[macro_export] +macro_rules! log_in { + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + format!("> {}", format!($($arg)*)) + ) + }; +} + +#[macro_export] +macro_rules! log_out { + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + format!("< {}", format!($($arg)*)) + ) + }; +} + +#[macro_export] +macro_rules! log_in_from { + ($sender:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal( + Some($sender), + format!("> {}", format!($($arg)*)) + ) + }; +} + +#[macro_export] +macro_rules! log_out_from { + ($sender:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal( + Some($sender), + format!("< {}", format!($($arg)*)) + ) + }; +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..1cd66a6 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,3 @@ +pub mod crypto_helper; +pub mod file_util; +pub mod logger; From 8a93f91e69fdff1a185042b6882eee4d10020f78 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 3 Jan 2026 21:40:21 +0100 Subject: [PATCH 006/220] Creation --- .gitignore | 10 +- Cargo.lock | 4462 ++++++++++++++++++++++++++++++ Cargo.toml | 42 + src/data/communication.rs | 454 +++ src/data/mod.rs | 1 + src/main.rs | 44 + src/server/api.rs | 177 ++ src/server/mod.rs | 5 + src/server/omikron_connection.rs | 258 ++ src/server/omikron_manager.rs | 0 src/server/server.rs | 475 ++++ src/server/socket.rs | 58 + src/sql/iota_omikron_tracker.rs | 22 + src/sql/mod.rs | 2 + src/sql/sql.rs | 450 +++ src/util/crypto_helper.rs | 143 + src/util/file_util.rs | 350 +++ src/util/logger.rs | 135 + src/util/mod.rs | 3 + 19 files changed, 7090 insertions(+), 1 deletion(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/data/communication.rs create mode 100644 src/data/mod.rs create mode 100644 src/main.rs create mode 100644 src/server/api.rs create mode 100644 src/server/mod.rs create mode 100644 src/server/omikron_connection.rs create mode 100644 src/server/omikron_manager.rs create mode 100644 src/server/server.rs create mode 100644 src/server/socket.rs create mode 100644 src/sql/iota_omikron_tracker.rs create mode 100644 src/sql/mod.rs create mode 100644 src/sql/sql.rs create mode 100644 src/util/crypto_helper.rs create mode 100644 src/util/file_util.rs create mode 100644 src/util/logger.rs create mode 100644 src/util/mod.rs diff --git a/.gitignore b/.gitignore index ad67955..e3d273c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,12 @@ target # 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/ +.idea/ + + +.env +/logs + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6bf40cc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4462 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Omega" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "async-tungstenite", + "axum", + "base64 0.22.1", + "bytes", + "color-eyre", + "dashmap", + "dotenv", + "futures", + "futures-util", + "hkdf", + "http-body-util", + "hyper", + "hyper-util", + "json", + "once_cell", + "pnet", + "rand 0.8.5", + "rand_core 0.6.4", + "reqwest", + "rustls 0.23.35", + "rustls-pemfile 2.2.0", + "sha1", + "sha2", + "sqlx", + "sysinfo", + "tokio", + "tokio-rustls", + "tokio-tungstenite", + "tokio-util", + "tower", + "tungstenite", + "uuid", + "walkdir", + "x448", + "zip", +] + +[[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 = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.1", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.3", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.6.0", + "async-lock 3.4.1", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-tls" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e" +dependencies = [ + "futures-core", + "futures-io", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "webpki-roots 0.22.6", +] + +[[package]] +name = "async-tungstenite" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" +dependencies = [ + "async-native-tls", + "async-std", + "async-tls", + "atomic-waker", + "futures-core", + "futures-io", + "futures-task", + "futures-util", + "gio", + "glib", + "log", + "native-tls", + "openssl", + "pin-project-lite", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-native-tls", + "tokio-openssl", + "tokio-rustls", + "tungstenite", + "webpki-roots 1.0.4", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[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.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "axum" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +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.1", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +dependencies = [ + "serde_core", +] + +[[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 = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-expr" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +dependencies = [ + "cc", +] + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[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" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +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 = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +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 = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[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 = "deflate64" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[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 = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[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" +dependencies = [ + "serde", +] + +[[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.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "fastrand" +version = "2.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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "libz-rs-sys", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[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", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "gio" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daeff3dd716d1ba91850b976b76a1c2d28f99ef6c1602cd8fdaa8fab8017fd9c" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", +] + +[[package]] +name = "gio-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.61.2", +] + +[[package]] +name = "glib" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" +dependencies = [ + "bitflags 2.10.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "gobject-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[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" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "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.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +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 0.23.35", + "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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.1", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "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 = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + +[[package]] +name = "iri-string" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +dependencies = [ + "memchr", + "serde", +] + +[[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.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +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 = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + +[[package]] +name = "libredox" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +dependencies = [ + "bitflags 2.10.0", + "libc", + "redox_syscall 0.6.0", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "libz-rs-sys" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +dependencies = [ + "zlib-rs", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lzma-rust2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +dependencies = [ + "crc", + "sha2", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[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 = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[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 2.11.1", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[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 = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "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.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "owo-colors" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[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 = "piper" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[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 = "pnet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_macros" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "pnet_macros_support" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", +] + +[[package]] +name = "pnet_sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "pnet_transport" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +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.4", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "redox_syscall" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "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 = "rsa" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.103.8", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[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.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +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.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "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_spanned" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +dependencies = [ + "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-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[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" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "async-io 1.13.0", + "async-std", + "base64 0.22.1", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.4.1", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.17", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "async-std", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.17", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64 0.22.1", + "bitflags 2.10.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.17", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.17", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[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.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +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 = "sysinfo" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" +dependencies = [ + "libc", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "windows", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "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 = "system-deps" +version = "7.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "target-lexicon" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand 2.3.0", + "getrandom 0.3.4", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.1", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +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-openssl" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" +dependencies = [ + "openssl", + "openssl-sys", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls 0.23.35", + "tokio", +] + +[[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", +] + +[[package]] +name = "tokio-util" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "hashbrown 0.15.5", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "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.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[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.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.9.2", + "rustls 0.23.35", + "rustls-pki-types", + "sha1", + "thiserror 2.0.17", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[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.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[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.2", +] + +[[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.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +dependencies = [ + "memchr", +] + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[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 = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +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" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..a1cb5fa --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "Omega" +version = "0.1.0" +edition = "2024" + +[dependencies] +aes-gcm = "*" +async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } +axum = "0.8.7" +base64 = "0.22.1" +bytes = "1.11.0" +color-eyre = "0.6.5" +dashmap = "6.1.0" +dotenv = "0.15.0" +futures = "0.3.31" +futures-util = "0.3.31" +hkdf = "0.12.4" +http-body-util = "0.1.3" +hyper = { version = "1.8.1", features = ["full"] } +hyper-util = "0.1.19" +json = "0.12.4" +once_cell = "1.21.3" +pnet = "0.35.0" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +reqwest = "0.12.28" +rustls = "0.23.35" +rustls-pemfile = "2.2.0" +sha1 = "0.10.6" +sha2 = "0.10.9" +sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } +sysinfo = "0.37.2" +tokio = { version = "*", features = ["full"] } +tokio-rustls = "0.26.4" +tokio-tungstenite = "0.28.0" +tokio-util = { version = "*", features = ["full"] } +tower = "0.5.2" +tungstenite = "0.28.0" +uuid = { version = "1.19.0", features = ["v4"] } +walkdir = "2.5.0" +x448 = "0.6.0" +zip = "6.0.0" diff --git a/src/data/communication.rs b/src/data/communication.rs new file mode 100644 index 0000000..2f8c689 --- /dev/null +++ b/src/data/communication.rs @@ -0,0 +1,454 @@ +use json::number::Number; +use json::{Array, JsonValue, object, parse}; +use std::collections::HashMap; +use std::time::{SystemTime, UNIX_EPOCH}; +use uuid::Uuid; + +#[derive(Eq, Hash, PartialEq, Clone, Debug)] +#[allow(non_camel_case_types, dead_code)] +pub enum DataTypes { + error_type, + accepted_ids, + uuid, + settings, + settings_name, + 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_token, + 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, + + user, +} + +impl DataTypes { + pub fn parse(p0: String) -> DataTypes { + let normalized = p0.to_lowercase().replace('_', ""); + + match normalized.as_str() { + "errortype" => DataTypes::error_type, + "chatpartnerid" => DataTypes::chat_partner_id, + "uuid" => DataTypes::uuid, + "settings" => DataTypes::settings, + "settingsname" => DataTypes::settings_name, + "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, + "calltoken" => DataTypes::call_token, + "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, + + "user" => DataTypes::user, + _ => DataTypes::error_type, // fallback if unknown + } + } +} + +#[derive(PartialEq, Clone, Debug)] +#[allow(non_camel_case_types, dead_code)] +pub enum CommunicationType { + error, + error_invalid_user_id, + error_not_found, + error_no_iota, + error_invalid_challenge, + error_invalid_secret, + error_invalid_private_key, + error_no_user_id, + error_no_call_id, + error_invalid_call_id, + success, + settings_save, + settings_load, + settings_list, + 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_disconnected, + client_closed, + public_key, + private_key, + webrtc_sdp, + webrtc_ice, + start_stream, + end_stream, + watch_stream, + call_token, + call_invite, + end_call, + function, + update, + create_user, +} +impl CommunicationType { + pub fn parse(p0: String) -> CommunicationType { + let normalized = p0.to_lowercase().replace('_', ""); + + match normalized.as_str() { + "watchstream" => CommunicationType::watch_stream, + "calltoken" => CommunicationType::call_token, + "callinvite" => CommunicationType::call_invite, + "endcall" => CommunicationType::end_call, + "function" => CommunicationType::function, + "update" => CommunicationType::update, + "createuser" => CommunicationType::create_user, + "errorinvaliduserid" => CommunicationType::error_invalid_user_id, + "errornotfound" => CommunicationType::error_not_found, + "errornoiota" => CommunicationType::error_no_iota, + "errorinvalidchallenge" => CommunicationType::error_invalid_challenge, + "errorinvalidsecret" => CommunicationType::error_invalid_secret, + "errorinvalidprivatekey" => CommunicationType::error_invalid_private_key, + "errornouserid" => CommunicationType::error_no_user_id, + "errornocallid" => CommunicationType::error_no_call_id, + "errorinvalidcallid" => CommunicationType::error_invalid_call_id, + "success" => CommunicationType::success, + "settingssave" => CommunicationType::settings_save, + "settingsload" => CommunicationType::settings_load, + "settingslist" => CommunicationType::settings_list, + "message" => CommunicationType::message, + "messagesend" => CommunicationType::message_send, + "messagelive" => CommunicationType::message_live, + "messageother_iota" => CommunicationType::message_other_iota, + "messagechunk" => CommunicationType::message_chunk, + "messagesget" => CommunicationType::messages_get, + "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, + "clientdisconnected" => CommunicationType::client_disconnected, + "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, + + _ => CommunicationType::error, + } + } +} + +#[derive(Debug, Clone)] +pub struct CommunicationValue { + pub id: Uuid, + pub comm_type: CommunicationType, + pub sender: i64, + pub receiver: i64, + pub data: HashMap, +} + +#[allow(dead_code)] +impl CommunicationValue { + pub fn new(comm_type: CommunicationType) -> Self { + Self { + id: Uuid::new_v4(), + comm_type, + sender: 0, + receiver: 0, + 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: i64) -> Self { + self.sender = sender; + self + } + pub fn get_sender(&self) -> i64 { + self.sender.clone() + } + pub fn with_receiver(mut self, receiver: i64) -> Self { + self.receiver = receiver; + self + } + pub fn get_receiver(&self) -> i64 { + 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()); + } + if self.sender > 0 && self.receiver > 0 { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + sender: self.sender, + receiver: self.receiver, + data: jdata + } + } else if self.sender > 0 { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + sender: self.sender, + data: jdata + } + } else if self.receiver > 0 { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + receiver: self.receiver, + data: jdata + } + } else { + object! { + id: self.id.to_string(), + type: format!("{:?}", self.comm_type), + data: jdata + } + } + } + + pub fn from_json(json_str: &str) -> Self { + if let Ok(parsed) = parse(json_str) { + let comm_type = CommunicationType::parse(parsed["type"].to_string()); + let mut sender: i64 = 0; + if parsed.has_key("sender") { + sender = parsed["sender"].as_i64().unwrap_or(0); + } + let mut receiver: i64 = 0; + if parsed.has_key("receiver") { + receiver = parsed["receiver"].as_i64().unwrap_or(0); + } + + 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, + } + } else { + Self { + id: Uuid::new_v4(), + comm_type: CommunicationType::error, + sender: 0, + receiver: 0, + data: HashMap::new(), + } + } + } + pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue { + let receiver = original + .get_data(DataTypes::receiver_id) + .unwrap_or(&JsonValue::Number(Number::from(0))) + .as_i64() + .unwrap_or(0); + + let now_ms = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + + let sender = original.get_sender(); + CommunicationValue::new(CommunicationType::message_other_iota) + .with_id(original.get_id()) + .with_receiver(receiver) + .add_data( + DataTypes::receiver_id, + JsonValue::Number(Number::from(receiver)), + ) + .with_sender(sender) + .add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string())) + .add_data( + DataTypes::sender_id, + JsonValue::Number(Number::from(sender)), + ) + .add_data( + DataTypes::content, + JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()), + ) + } +} diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..73c2657 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1 @@ +pub mod communication; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..01b51b8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,44 @@ +mod data; +mod server; +mod sql; +mod util; + +use crate::sql::sql::initialize_db; +use crate::sql::sql::print_users; +use crate::util::crypto_helper::load_public_key; +use crate::util::crypto_helper::load_secret_key; +use crate::util::logger::startup; +use dotenv::dotenv; +use once_cell::sync::Lazy; +use std::env; + +static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); +pub fn get_private_key() -> x448::Secret { + load_secret_key(&*PRIVATE_KEY).unwrap() +} +static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); +pub fn get_public_key() -> x448::PublicKey { + load_public_key(&*PUBLIC_KEY).unwrap() +} + +#[tokio::main] +async fn main() { + dotenv().ok(); + startup(); + log!("Started"); + log_in!("Incoming messages"); + log_out!("Outgoing messages"); + log!(" .env"); + if let Err(e) = initialize_db().await { + log!("[FATAL] Database initialization failed: {}", e); + log!( + "[FATAL] Please ensure the database is running and the .env file is configured correctly." + ); + return; + } + let _ = print_users().await; + log!(" DB"); + server::server::start(9187).await; + log!(" Server"); + loop {} +} diff --git a/src/server/api.rs b/src/server/api.rs new file mode 100644 index 0000000..db464a2 --- /dev/null +++ b/src/server/api.rs @@ -0,0 +1,177 @@ +use crate::{get_public_key, log}; +use crate::{ + sql::{ + iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, + sql::{ + change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, + change_keys, change_status, change_username, get_by_id, get_by_username, + get_iota_by_id, get_omikron_by_id, get_random_omikron, get_register_id, + register_complete_iota, register_complete_user, + }, + }, + util::crypto_helper::public_key_to_base64, +}; +use axum::http::HeaderValue; +use http_body_util::Full; +use hyper::body::Bytes; +use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; +use json::JsonValue; + +pub async fn handle( + path: &str, + headers: HeaderMap, + body_string: Option, +) -> HttpResponse> { + let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); + + let body: Option = if body_string.is_some() { + if let Ok(body_json) = json::parse(&body_string.unwrap()) { + Some(body_json) + } else { + None + } + } else { + None + }; + // api/ + // get/ + // omikron/ + // id/ + // register/ + // innit/ + // complete + log!("{}", path); + log!("{:?} .len = {}", path_parts, path_parts.len()); + let (status, content, body_text) = if path_parts.len() >= 2 { + match path_parts[1] { + "get" => match path_parts[2] { + // api/get/omikron -> any omikron + // api/get/omikron/ -> omikron for id (user / iota / omikron) + "omikron" => { + if path_parts.len() == 3 { + if let Ok((id, public_key, ip_address)) = get_random_omikron().await { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else if path_parts.len() == 4 { + let id = path_parts[3].parse::().unwrap_or(0); + if id == 0 { + not_found() + } else if let Ok((omikron_id, public_key, ip_address)) = + get_omikron_by_id(id).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else if let Some(omikron_id) = get_omikron_for_iota(id).await { + if let Ok((omikron_id, public_key, ip_address)) = + get_omikron_by_id(omikron_id).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_id(id).await + { + if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { + if let Ok((omikron_id, public_key, ip_address)) = + get_omikron_by_id(omikron_id).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else { + not_found() + } + } else { + not_found() + } + } else { + bad_request() + } + } + // get/id/ + "id" => { + let username = path_parts[3]; + bad_request() + } + "public_key" => ( + StatusCode::OK, + "application/json", + public_key_to_base64(&get_public_key()), + ), + _ => { + let id = path_parts[2]; + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else { + bad_request() + } + } + }, + _ => not_found(), + } + } else { + not_found() + }; + let body = Full::new(Bytes::from(body_text.to_string())); + HttpResponse::builder().status(status).body(body).unwrap() +} +pub fn bad_request() -> (StatusCode, &'static str, String) { + ( + StatusCode::BAD_REQUEST, + "text/text", + "400 Bad Request".to_string(), + ) +} +pub fn unauthorized() -> (StatusCode, &'static str, String) { + ( + StatusCode::UNAUTHORIZED, + "text/text", + "401 Unauthorized".to_string(), + ) +} +pub fn forbidden() -> (StatusCode, &'static str, String) { + ( + StatusCode::FORBIDDEN, + "text/text", + "403 Forbidden".to_string(), + ) +} +pub fn not_found() -> (StatusCode, &'static str, String) { + ( + StatusCode::NOT_FOUND, + "text/text", + "404 Not Found".to_string(), + ) +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..39df0d8 --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,5 @@ +pub mod api; +pub mod omikron_connection; +pub mod omikron_manager; +pub mod server; +pub mod socket; diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs new file mode 100644 index 0000000..5aafd16 --- /dev/null +++ b/src/server/omikron_connection.rs @@ -0,0 +1,258 @@ +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::get_public_key; +use crate::sql::sql::get_omikron_by_id; +use crate::util::crypto_helper::encrypt; +use crate::{get_private_key, log_in_from, log_out_from}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use dashmap::DashMap; +use futures::SinkExt; +use futures::stream::SplitSink; +use futures::stream::SplitStream; +use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; +use json::JsonValue; +use rand::Rng; +use rand::distributions::Alphanumeric; +use std::sync::Arc; +use tokio::sync::RwLock; +use tokio_tungstenite::WebSocketStream; +use tungstenite::Message; +use tungstenite::Utf8Bytes; +use uuid::Uuid; +use x448::PublicKey; + +pub struct OmikronConnection { + pub sender: Arc>, Message>>>, + pub receiver: Arc>>>>, + pub omikron_id: Arc>, + pub pub_key: Arc>>>, + identified: Arc>, + challenged: Arc>, + challenge: Arc>, + pub ping: Arc>, + waiting_tasks: DashMap< + Uuid, + Box, CommunicationValue) -> bool + Send + Sync>, + >, +} + +impl OmikronConnection { + pub fn new( + sender: SplitSink>, Message>, + receiver: SplitStream>>, + ) -> Arc { + Arc::new(Self { + sender: Arc::new(RwLock::new(sender)), + receiver: Arc::new(RwLock::new(receiver)), + omikron_id: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + identified: Arc::new(RwLock::new(false)), + challenged: Arc::new(RwLock::new(false)), + challenge: Arc::new(RwLock::new(String::new())), + ping: Arc::new(RwLock::new(-1)), + waiting_tasks: DashMap::new(), + }) + } + pub async fn send_message(&self, message: &CommunicationValue) { + let mut sender = self.sender.write().await; + let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string())); + log_out_from!(*self.omikron_id.read().await, "{}", message_text); + sender.send(message_text).await.unwrap(); + } + pub async fn get_user_id(&self) -> i64 { + *self.omikron_id.read().await + } + pub async fn is_identified(&self) -> bool { + *self.identified.read().await && *self.challenged.read().await + } + pub async fn get_public_key(&self) -> PublicKey { + PublicKey::from_bytes(self.pub_key.read().await.as_ref().unwrap()).unwrap() + } + + pub async fn handle_message(self: Arc, message: String) { + let cv = CommunicationValue::from_json(&message); + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + + log_in_from!(*self.omikron_id.read().await, "{}", message); + + if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { + let omikron_id = cv + .get_data(DataTypes::omikron) + .unwrap_or(&JsonValue::Null) + .as_i64() + .unwrap_or(0); + if let Ok((_, public_key, _)) = get_omikron_by_id(omikron_id).await { + // Generate Challenge, encrypt it and send it to the omikron + *self.omikron_id.write().await = omikron_id; + *self.identified.write().await = true; + + let challenge_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge_str.clone(); + + let user_public_key_bytes = match STANDARD.decode(&public_key) { + Ok(bytes) => bytes, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_user_id, + ) + .await; + return; + } + }; + *self.pub_key.write().await = Some(user_public_key_bytes.clone()); + + let omikron_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) + { + Some(key) => key, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_user_id, + ) + .await; + return; + } + }; + + let encrypted_challenge = + encrypt(get_private_key(), omikron_pub_key, &challenge_str) + .unwrap_or("".to_string()); + + let response = CommunicationValue::new(CommunicationType::challenge) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), + ) + .add_data_str(DataTypes::challenge, encrypted_challenge) + .with_id(cv.get_id()); + + self.send_message(&response).await; + // prepare Challenge Response handling + self.waiting_tasks.insert( + cv.get_id(), + Box::new( + |selfc: Arc, cv: CommunicationValue| -> bool { + tokio::spawn(async move { + let client_challenge_response_b64 = + match cv.get_data(DataTypes::challenge) { + Some(data) => data.to_string(), + None => { + selfc + .send_error_response( + &cv.get_id(), + CommunicationType::error, + ) + .await; + return; + } + }; + + let challenge_response_bytes = + match STANDARD.decode(&client_challenge_response_b64) { + Ok(bytes) => bytes, + Err(_) => { + selfc + .send_error_response( + &cv.get_id(), + CommunicationType::error, + ) + .await; + return; + } + }; + + if challenge_response_bytes.len() < 12 { + selfc + .send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + + let client_response = cv + .get_data(DataTypes::challenge) + .unwrap_or(&JsonValue::Null) + .as_str() + .unwrap_or(""); + + let expected_challenge = selfc.challenge.read().await.clone(); + + if client_response != expected_challenge { + selfc + .send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_challenge, + ) + .await; + selfc.close().await; + return; + } + + *selfc.challenged.write().await = true; + + let response = CommunicationValue::new( + CommunicationType::identification_response, + ) + .with_id(cv.get_id()); + + selfc.send_message(&response).await; + return; + }); + return true; + }, + ), + ); + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + .await; + } + return; + } + + if self.waiting_tasks.contains_key(&cv.get_id()) { + let (_, task) = self.waiting_tasks.remove(&cv.get_id()).unwrap(); + let _ = task(self.clone(), cv.clone()); + } + + if !self.is_identified().await { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + .await; + return; + } + } + + 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; + } + pub async fn close(&self) { + let mut sender = self.sender.write().await; + let _ = sender.close().await; + } + pub async fn handle_close(self: Arc) { + if self.is_identified().await { + if self.get_user_id().await != 0 {} + } + } + + async fn handle_ping(&self, cv: CommunicationValue) { + 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; + } + } + + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + + self.send_message(&response).await; + } +} diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/server/server.rs b/src/server/server.rs new file mode 100644 index 0000000..7dc19ab --- /dev/null +++ b/src/server/server.rs @@ -0,0 +1,475 @@ +use crate::log; +use crate::server::api; +use crate::server::omikron_connection::OmikronConnection; +use crate::util::file_util::load_file_buf; + +use base64::Engine; +use base64::engine::general_purpose::STANDARD; +use bytes::Bytes; +use futures::StreamExt; +use futures_util::TryFutureExt; +use http_body_util::BodyExt; +use http_body_util::Full; +use hyper::{Method, StatusCode}; +use hyper::{ + Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade, +}; +use hyper_util::rt::tokio::TokioIo; +use hyper_util::service::TowerToHyperService; +use pnet::datalink::NetworkInterface; +use rustls::ServerConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use sha1::{Digest, Sha1}; +use std::error::Error; +use std::io::ErrorKind; +use std::io::{self, BufReader}; +use std::net::SocketAddr; +use std::result::Result::Ok; +use std::sync::Arc; +use std::{future::Future, pin::Pin, time::Duration}; +use tokio::net::TcpListener; +use tokio::sync::broadcast; +use tokio_rustls::TlsAcceptor; +use tokio_tungstenite::{WebSocketStream, accept_async}; +use tower::Service; +use tungstenite::Message; +#[derive(Clone)] +struct HttpService { + peer_addr: SocketAddr, +} + +impl Service> for HttpService { + type Response = HttpResponse>; + type Error = io::Error; + type Future = Pin> + Send>>; + + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(std::io::Result::Ok(())) + } + + fn call(&mut self, req: HttpRequest) -> Self::Future { + let _peer_ip = self.peer_addr.ip(); + + let (parts, body) = req.into_parts(); + + let method = parts.method.clone(); + let path = parts.uri.path().to_string(); + let headers = parts.headers.clone(); + + let fut = async move { + let is_websocket_upgrade = path == "/ws/omikron" + && method == Method::GET + && headers + .get("connection") + .map(|v| { + v.to_str() + .unwrap_or("") + .split(',') + .any(|s| s.trim().eq_ignore_ascii_case("upgrade")) + }) + .unwrap_or(false) + && headers + .get("upgrade") + .map(|v| v.to_str().unwrap_or("").eq_ignore_ascii_case("websocket")) + .unwrap_or(false); + + if is_websocket_upgrade { + log!("Attempting WebSocket upgrade on {}", path); + + if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { + let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string(); + let sec_websocket_accept = calculate_accept_key(&sec_websocket_key); + + let response = HttpResponse::builder() + .status(StatusCode::SWITCHING_PROTOCOLS) + .header("Upgrade", "websocket") + .header("Connection", "Upgrade") + .header("Sec-WebSocket-Accept", sec_websocket_accept) + .body(Full::new(Bytes::from(""))) + .unwrap(); + let req_for_upgrade = HttpRequest::from_parts(parts, body); + let upgrades = upgrade::on(req_for_upgrade); + log!("Handling WebSocket upgrade"); + + // Spawn upgrade handling to avoid blocking the service call + tokio::spawn(async move { + match upgrades.await { + Ok(upgraded_stream) => { + log!("Valid WebSocket upgrade"); + let raw_stream = TokioIo::new(upgraded_stream); + + let ws_stream = WebSocketStream::from_raw_socket( + raw_stream, + tungstenite::protocol::Role::Server, + None, + ) + .await; + log!( + "WebSocket handshake successful, handling connection for Omikron" + ); + + // Split stream for OmikronConnection + let (writer, reader) = ws_stream.split(); + + // INTEGRATION START + // Erstelle die Connection und starte den Handler + let connection = OmikronConnection::new(writer, reader); + + // Handler in separatem Task starten + start_omikron_handler(connection).await; + // INTEGRATION END + } + Err(e) => { + log!("WebSocket upgrade failed after response: {:?}", e); + } + } + }); + + log!("Handled WebSocket connection initiation"); + Ok(response) + } else { + log!("No Sec-WebSocket-Key found in request headers"); + let response = HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) + .unwrap(); + Ok(response) + } + } else if path.starts_with("/api") { + let whole_body = match body.collect().await { + Ok(collected) => collected, + Err(e) => { + log!("Error collecting body: {}", e); + return Ok(HttpResponse::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Full::new(Bytes::from(format!( + "Failed to read body: {}", + e + )))) + .unwrap()); + } + }; + let bytes = whole_body.to_bytes(); + + let body_string: Option = match String::from_utf8(bytes.to_vec()) { + Ok(s) => Some(s), + Err(_) => None, + }; + + Ok(api::handle(&path, headers.clone(), body_string).await) + } else { + let response = HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::new(Bytes::from("No path provided"))) + .unwrap(); + Ok(response) + } + }; + + Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { + io::Error::new( + io::ErrorKind::Other, + format!("Error in request handling: {}", err), + ) + })) + } +} + +async fn run_http_server(port: u16) -> bool { + let ip = "0.0.0.0".to_string(); + let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener { + log!("Failed to bind to port {}: {:?}", port, e); + return false; + } + let listener = listener.unwrap(); + log!( + "Standard Server listening for HTTP and WS on {}:{}", + ip, + port + ); + + // Create a broadcast channel for graceful shutdown signal + let (shutdown_tx, _) = broadcast::channel::<()>(1); + + tokio::spawn(async move { + loop { + tokio::select! { + + accepted = listener.accept() => { + match accepted { + std::result::Result::Ok((stream, addr)) => { + let service = HttpService { peer_addr: addr }; + let io = TokioIo::new(stream); + + // Subscribe to the shutdown signal for this specific connection + let mut rx = shutdown_tx.subscribe(); + + tokio::spawn(async move { + // Prepare the connection future + let conn = http1::Builder::new() + .preserve_header_case(true) + .title_case_headers(true) + .serve_connection(io, TowerToHyperService::new(service)) + .with_upgrades(); + + // Wait for either the connection to finish naturally OR the shutdown signal + tokio::select! { + res = conn => { + if let Err(err) = res { + if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { + if io_err.kind() != io::ErrorKind::ConnectionReset + && io_err.kind() != io::ErrorKind::BrokenPipe + { + log!("Error serving connection: {:?}", err); + } + } + } + } + _ = rx.recv() => { + // Shutdown signal received. + // Dropping the 'conn' future here closes the socket immediately. + } + } + }); + } + Err(e) => { + log!("Error accepting connection: {:?}", e); + tokio::time::sleep(Duration::from_millis(500)).await; + } + } + } + } + } + }); + + true +} + +/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config. +async fn run_tls_server(port: u16, tls_config: Arc) -> bool { + let mut ip = "0.0.0.0".to_string(); + for iface in pnet::datalink::interfaces() { + let iface: NetworkInterface = iface; + let ipsv = format!("{}", iface.ips[0]); + let ips: &str = ipsv.split('/').next().unwrap(); + log!("{}", ips.to_string()); + if format!("{}", ips).starts_with("10.") { + ip = ips.to_string(); + } + } + + let acceptor = TlsAcceptor::from(tls_config); + + let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener { + log!("Failed to bind to port {}: {:?}", port, e); + return false; + } + let listener = listener.unwrap(); + log!( + "Encrypted Server listening for HTTPS and WSS on {}:{}", + ip, + port + ); + + // Create a broadcast channel for graceful shutdown signal + let (shutdown_tx, _) = broadcast::channel::<()>(1); + + tokio::spawn(async move { + loop { + tokio::select! { + // Monitor for shutdown signal + _ = async { + loop { + tokio::time::sleep(Duration::from_millis(100)).await; + } + } => { + log!("Encrypted Server received shutdown signal."); + // Send kill signal to all active connection tasks + let _ = shutdown_tx.send(()); + break; + } + + // Accept new connections + accepted = listener.accept() => { + match accepted { + std::result::Result::Ok((stream, addr)) => { + let service = HttpService { peer_addr: addr }; + let acceptor = acceptor.clone(); + + // Subscribe to the shutdown signal for this specific connection + let mut rx = shutdown_tx.subscribe(); + + tokio::spawn(async move { + // Perform TLS handshake + let tls_stream = match acceptor.accept(stream).await { + Ok(s) => s, + Err(e) => { + if e.kind() != io::ErrorKind::Interrupted { + log!("TLS Handshake error: {:?}", e); + } + return; + } + }; + let io = TokioIo::new(tls_stream); + + // Prepare connection future + let conn = http1::Builder::new() + .preserve_header_case(true) + .title_case_headers(true) + .serve_connection(io, TowerToHyperService::new(service)) + .with_upgrades(); + + // Wait for either the connection to finish naturally OR the shutdown signal + tokio::select! { + res = conn => { + if let Err(err) = res { + if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { + if io_err.kind() != io::ErrorKind::ConnectionReset + && io_err.kind() != io::ErrorKind::BrokenPipe + { + log!("Error serving connection: {:?}", err); + } + } + } + } + _ = rx.recv() => { + // Shutdown signal received. + // Dropping the 'conn' future here closes the socket immediately. + } + } + }); + } + Err(e) => { + log!("Error accepting connection: {:?}", e); + tokio::time::sleep(Duration::from_millis(500)).await; + } + } + } + } + } + + log!("Encrypted Server shutdown complete."); + }); + true +} + +pub async fn start(port: u16) -> bool { + let tls_result = load_tls_config(); + + match tls_result { + Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, + Ok(None) => run_http_server(port).await, + Err(e) => { + log!("Fatal error during TLS config load: {}", e); + false + } + } +} +fn calculate_accept_key(key: &str) -> String { + let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + let mut sha1 = Sha1::new(); + sha1.update(key.as_bytes()); + sha1.update(websocket_guid.as_bytes()); + let result = sha1.finalize(); + STANDARD.encode(result) // Base64 encode the result +} + +/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. +fn load_tls_config() -> Result>, Box> { + let cert_file_res = load_file_buf("certs", "cert.pem"); + let key_file_res = load_file_buf("certs", "cert.key"); + + // Check if certificate files are present. If not, return None. + let cert_file_buf = match cert_file_res { + Ok(b) => b, + Err(e) if e.kind() == ErrorKind::NotFound => { + log!("TLS certificate 'certs/cert.pem' not found."); + return Ok(None); + } + Err(e) => return Err(e.into()), // Other IO error + }; + + let key_file_buf = match key_file_res { + Ok(b) => b, + Err(e) if e.kind() == ErrorKind::NotFound => { + log!("TLS key 'certs/cert.key' not found."); + return Ok(None); + } + Err(e) => return Err(e.into()), // Other IO error + }; + + // Continue with configuration if both files were found + let mut cert_reader = BufReader::new(cert_file_buf); + let cert_ders = rustls_pemfile::certs(&mut cert_reader) + .collect::, io::Error>>()?; + + // PKCS8 + let mut key_reader = BufReader::new(key_file_buf); + let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) + .map(|r| r.map(Into::into)) // Explicit conversion + .collect::, io::Error>>()?; + + if key_ders.is_empty() { + // RSA + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) + .map(|r| r.map(Into::into)) + .collect::, io::Error>>()?; + } + + if key_ders.is_empty() { + // EC + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) + .map(|r| r.map(Into::into)) + .collect::, io::Error>>()?; + } + + if key_ders.is_empty() { + return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); + } + + let config = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_ders, key_ders.remove(0)) + .map_err(|e| io::Error::new(ErrorKind::Other, e.to_string()))?; + + Ok(Some(Arc::new(config))) +} +// In deiner Server-Logik, wo OmikronConnection initialisiert wird: + +pub async fn start_omikron_handler(connection: Arc) { + loop { + let msg = match { + let mut receiver = connection.receiver.write().await; + receiver.next().await + } { + Some(Ok(msg)) => msg, + Some(Err(e)) => { + log!("WS Error: {}", e); + break; + } + None => break, // Stream ended + }; + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + // Other message types like Binary, Ping, Pong are ignored. + _ => {} + } + } + connection.handle_close().await; +} diff --git a/src/server/socket.rs b/src/server/socket.rs new file mode 100644 index 0000000..2ff5966 --- /dev/null +++ b/src/server/socket.rs @@ -0,0 +1,58 @@ +use std::sync::Arc; + +use futures::StreamExt; +use futures::stream::SplitSink; +use futures::stream::SplitStream; +use hyper::upgrade::Upgraded; +use hyper_util::rt::TokioIo; +use tungstenite::Message; + +use crate::log; +use crate::server::omikron_connection::OmikronConnection; + +pub fn handle( + path: String, + writer: SplitSink>, Message>, + reader: SplitStream>>, +) { + log!("handling"); + tokio::spawn(async move { + if path.starts_with("/ws/phi/") { + } else if path.starts_with("/ws/omikron/") { + let community_conn: Arc = + Arc::from(OmikronConnection::new(writer, reader)); + loop { + let msg_result: Option> = { + let mut session_lock = community_conn.receiver.write().await; + session_lock.next().await + }; + + match msg_result { + Some(Ok(msg)) => { + if msg.is_text() { + let text = msg.into_text().unwrap(); + community_conn + .clone() + .handle_message(text.to_string()) + .await; + } else if msg.is_close() { + log!("Closing: {}", msg); + community_conn.handle_close().await; + return; + } + } + Some(Err(e)) => { + log!("Closing ERR: {}", e); + community_conn.handle_close().await; + return; + } + None => { + log!("Closed Session me!"); + community_conn.handle_close().await; + return; + } + } + } + } + }); +} diff --git a/src/sql/iota_omikron_tracker.rs b/src/sql/iota_omikron_tracker.rs new file mode 100644 index 0000000..7d768ca --- /dev/null +++ b/src/sql/iota_omikron_tracker.rs @@ -0,0 +1,22 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +static IOTA_OMIKRON_MAP: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn track_iota_omikron(iota: i64, omikron: i64) { + let mut c = IOTA_OMIKRON_MAP.write().await; + c.insert(iota, omikron); +} + +pub async fn get_omikron_for_iota(iota: i64) -> Option { + let c = IOTA_OMIKRON_MAP.read().await; + c.get(&iota).cloned() +} + +pub async fn untrack_iota(iota: i64) { + let mut c = IOTA_OMIKRON_MAP.write().await; + c.remove(&iota); +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs new file mode 100644 index 0000000..9ef349a --- /dev/null +++ b/src/sql/mod.rs @@ -0,0 +1,2 @@ +pub mod iota_omikron_tracker; +pub mod sql; diff --git a/src/sql/sql.rs b/src/sql/sql.rs new file mode 100644 index 0000000..de749fa --- /dev/null +++ b/src/sql/sql.rs @@ -0,0 +1,450 @@ +use crate::log; +use once_cell::sync::Lazy; +use sqlx::{MySql, Pool, mysql::MySqlPoolOptions}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{ + env, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; +use tokio::sync::RwLock; + +/* +use crate::sql::{ + iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, + sql::{ + change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, + change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, + get_register_id, register_complete_iota, register_complete_user, + }, +}; + */ + +static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); + +pub async fn connect() -> Result, sqlx::Error> { + let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); + let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); + let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); + + MySqlPoolOptions::new() + .max_connections(5) + .connect(&format!( + "mysql://{}:{}@127.0.0.1:3306/{}", + user, passwd, table + )) + .await +} +// Omega +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +pub async fn initialize_db() -> Result<(), sqlx::Error> { + let pool = connect().await?; + let mut db_lock = SQL_DB.write().await; + // create tables + // with indexes + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + users ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, + display VARCHAR(15) COLLATE utf8mb4_bin, + status VARCHAR(15) COLLATE utf8mb4_bin, + about VARCHAR(200) COLLATE utf8mb4_bin, + avatar MEDIUMBLOB, + sub_level INT(11) NOT NULL DEFAULT 0, + sub_end BIGINT(20) NOT NULL, + public_key TEXT NOT NULL COLLATE utf8mb4_bin, + private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin, + iota_id BIGINT UNSIGNED NOT NULL, + token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + iotas ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + public_key TEXT NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + omikrons ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + is_active INT(1) NOT NULL DEFAULT 0, + public_key TEXT NOT NULL COLLATE utf8mb4_bin, + location TEXT NOT NULL COLLATE utf8mb4_bin, + ip_address TEXT NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + + *db_lock = Some(pool); + Ok(()) +} + +// ========================================================================================== +// REGISTER +// ========================================================================================== +pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); +pub static CURRENT_REGISTER_PROCESS: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); + +pub async fn get_register_id() -> u64 { + let mut current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + loop { + let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); + + if current_locked < current_time { + let result = CURRENT_MILLI_USED.compare_exchange( + current_locked, // expected value + current_time, // new value + Ordering::SeqCst, // acquire/release ordering + Ordering::SeqCst, // failure ordering + ); + + match result { + Ok(_) => { + CURRENT_REGISTER_PROCESS.write().await.push(current_time); + return current_time; + } + Err(_) => { + continue; + } + } + } else { + current_time = current_locked + 1; + } + } +} + +// ========================================================================================== +// USERS +// ========================================================================================== + +pub async fn get_by_username( + username: &str, +) -> Result< + ( + i64, + i64, + String, + String, + String, + String, + String, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( + "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", + ) + .bind(username) + .fetch_optional(pool) + .await?.ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn get_by_id( + id: i64, +) -> Result< + ( + i64, + i64, + String, + String, + String, + String, + String, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( + "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", + ) + .bind(id) + .fetch_optional(pool) + .await?.ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET username = ? WHERE id = ?") + .bind(new_username) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_display_name(id: i64, new_display_name: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET display_name = ? WHERE id = ?") + .bind(new_display_name) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") + .bind(new_avatar) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET about = ? WHERE id = ?") + .bind(new_about) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET status = ? WHERE id = ?") + .bind(new_status) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") + .bind(new_iota_id) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn change_keys( + id: i64, + new_public_key: String, + new_private_key_hash: String, +) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn register_complete_user( + id: i64, + username: String, + public_key: String, + private_key_hash: String, + iota_id: i64, + reset_token: String, +) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, reset_token) VALUES (?, ?, ?, ?, ?, ?)") + .bind(id) + .bind(username) + .bind(public_key) + .bind(private_key_hash) + .bind(iota_id) + .bind(reset_token) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn print_users() -> Result<(), Box> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + log!("Printing users..."); + for (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) + in sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( + "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users", + ) + .fetch_all(pool) + .await? + .iter() + { + log!( + "User: {:?}", + (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) + ); + } + + Ok(()) +} + +// ========================================================================================== +// IOTA +// ========================================================================================== +pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") + .bind(id) + .bind(public_key) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM users WHERE id = ?") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, String)>("SELECT id, public_key FROM iotas WHERE id = ?") + .bind(id) + .fetch_optional(pool) + .await? + .ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + .bind(new_key) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM iotas WHERE id = ?") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} + +// ========================================================================================== +// OMIKRONS +// ========================================================================================== + +pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + let a = sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") + .fetch_optional(pool) + .await? + .ok_or_else(|| sqlx::Error::RowNotFound); + // this log isn't printing + log!("Random Omikron: {:?}", a); + a +} + +pub async fn get_omikron_by_id(id: i64) -> Result<(i64, String, String), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE id = ?") + .bind(id) + .fetch_optional(pool) + .await? + .ok_or_else(|| sqlx::Error::RowNotFound) +} + +pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + let active = if active { 1 } else { 0 }; + + sqlx::query("UPDATE omikrons SET active = ? WHERE id = ?") + .bind(active) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs new file mode 100644 index 0000000..bb086b8 --- /dev/null +++ b/src/util/crypto_helper.rs @@ -0,0 +1,143 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, OsRng}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use rand_core::RngCore; +use sha2::{Digest, Sha256}; +use x448::{PublicKey, Secret, SharedSecret}; + +/// Errors for crypto operations +#[derive(Debug)] +pub enum CryptoError { + Base64Decode(base64::DecodeError), + InvalidKey, + AgreementError, + EncryptionError(aes_gcm::Error), + DecryptionError(aes_gcm::Error), +} + +impl From for CryptoError { + fn from(err: base64::DecodeError) -> Self { + CryptoError::Base64Decode(err) + } +} + +pub struct KeyPair { + pub secret: Secret, + pub public: PublicKey, +} + +pub fn generate_keypair() -> KeyPair { + let mut buf = [0u8; 56]; + let mut rng = OsRng; + rng.fill_bytes(&mut buf); + let secret = Secret::from_bytes(&buf).unwrap(); + let public = PublicKey::from(&secret); + KeyPair { secret, public } +} + +pub fn public_key_to_base64(pubkey: &PublicKey) -> String { + STANDARD.encode(pubkey.as_bytes().as_ref()) +} + +pub fn secret_key_to_base64(secret: &Secret) -> String { + STANDARD.encode(secret.as_bytes().as_ref()) +} + +pub fn load_public_key(base64_pub: &str) -> Option { + let bytes = STANDARD.decode(base64_pub).unwrap(); + PublicKey::from_bytes(&bytes) +} + +pub fn load_secret_key(base64_secret: &str) -> Option { + let bytes = STANDARD.decode(base64_secret).unwrap(); + Secret::from_bytes(&bytes) +} + +fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(shared.as_bytes()); + let result = hasher.finalize(); + let mut key = [0u8; 32]; + key.copy_from_slice(&result[..32]); + key +} + +pub fn encrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + plaintext: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + encrypt(secret, peer_pub, plaintext) +} +pub fn encrypt( + secret: Secret, + peer_pub: PublicKey, + plaintext: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + let mut nonce_bytes = [0u8; 12]; + OsRng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + let ciphertext = cipher + .encrypt(nonce, plaintext.as_bytes()) + .map_err(CryptoError::EncryptionError)?; + // prefix nonce to ciphertext + let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); + out.extend_from_slice(&nonce_bytes); + out.extend_from_slice(&ciphertext); + Ok(STANDARD.encode(&out)) +} + +pub fn decrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + encrypted_base64: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + decrypt(secret, peer_pub, encrypted_base64) +} +pub fn decrypt( + secret: Secret, + peer_pub: PublicKey, + encrypted_base64: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + + let encrypted = STANDARD.decode(encrypted_base64)?; + if encrypted.len() < 12 { + return Err(CryptoError::DecryptionError(aes_gcm::Error)); + } + let nonce_bytes = &encrypted[..12]; + let ciphertext = &encrypted[12..]; + let nonce = Nonce::from_slice(nonce_bytes); + let plaintext_bytes = cipher + .decrypt(nonce, ciphertext) + .map_err(CryptoError::DecryptionError)?; + let plaintext = String::from_utf8(plaintext_bytes) + .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; + Ok(plaintext) +} + +pub fn hash_it(input: &str) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(input.as_bytes()); + hasher.finalize().to_vec() +} + +pub fn hex_hash(input: &str) -> String { + let digest = hash_it(input); + digest.iter().map(|b| format!("{:02x}", b)).collect() +} diff --git a/src/util/file_util.rs b/src/util/file_util.rs new file mode 100644 index 0000000..383ba3b --- /dev/null +++ b/src/util/file_util.rs @@ -0,0 +1,350 @@ +use crate::log; +use std::ffi::OsStr; +use std::fs::{self, File}; +use std::io::{self, BufReader, Read}; +use std::path::{Path, PathBuf}; +use sysinfo::System; +use uuid::Uuid; +use walkdir::WalkDir; +use zip::ZipArchive; + +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) { + log!( + "[IMPORTANT] Couldn't delete directory {}: {}", + directory.display(), + e + ); + return false; + } + true +} + +pub fn load_file_buf(path: &str, name: &str) -> io::Result> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + // Ensure the directory exists, create if necessary + if !dir.exists() { + if let Err(_) = fs::create_dir_all(&dir) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Directory creation failed", + )); + } + } + + // Create the file if it doesn't exist + if !file_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "File creation failed", + )); + } + + // Open the file and return a BufReader for efficient reading + let file = File::open(&file_path)?; + Ok(BufReader::new(file)) +} +pub fn has_file(path: &str, name: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + return false; + } + + if !file_path.exists() { + return false; + } + + true +} +pub fn has_dir(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + + if !dir.exists() { + return false; + } + + true +} + +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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return String::new(); + } + return String::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log!("[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 load_file_vec(path: &str, name: &str) -> Vec { + 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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return Vec::new(); + } + return Vec::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log!("[IMPORTANT] Couldn't create file: {}", e); + } + return Vec::new(); + } + + let mut content = Vec::new(); + if let Ok(mut f) = File::open(&file_path) { + let _ = f.read_to_end(&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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return; + } + } + + if let Err(e) = fs::write(&file_path, value) { + log!( + "[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 used_dir_space(path: &str) -> u64 { + get_directory_size(&PathBuf::from(format!("{}/{}", get_directory(), path))) +} + +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)) +} + +// Helper to download the zip file content to a file on disk +async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { + let response = reqwest::get(url).await?; + + // Check for successful response status + if !response.status().is_success() { + return Err(format!("Failed to download file: Status {}", response.status()).into()); + } + + let mut zip_file = File::create(as_name)?; + let body = response.bytes().await?; + io::copy(&mut &*body, &mut zip_file)?; + + Ok(()) +} + +fn extract_zip_contents_to_folder( + zip_path: &Path, + target_dir: &Path, +) -> Result<(), Box> { + let file = File::open(zip_path)?; + let mut archive = ZipArchive::new(file)?; + + let staging_dir = target_dir.with_extension("staging"); + + let _ = fs::remove_dir_all(&staging_dir); + fs::create_dir_all(&staging_dir)?; + + let mut first_item_name: Option = None; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let entry_path = staging_dir.join(file.sanitized_name()); + + if i == 0 { + if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { + first_item_name = Some(file.sanitized_name()); + } + } + + if file.name().ends_with('/') { + fs::create_dir_all(&entry_path)?; + } else { + if let Some(parent) = entry_path.parent() { + fs::create_dir_all(parent)?; + } + let mut out_file = File::create(entry_path)?; + io::copy(&mut file, &mut out_file)?; + } + } + + if let Some(root_path) = first_item_name { + let root_dir = staging_dir.join(&root_path); + + if root_dir.is_dir() { + let root_contents_count = fs::read_dir(&staging_dir)?.count(); + + if root_contents_count == 1 + || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) + { + let _ = fs::remove_dir_all(target_dir); + fs::create_dir_all(target_dir)?; + + for entry in fs::read_dir(root_dir)? { + let entry = entry?; + let src = entry.path(); + let dest = target_dir.join(entry.file_name()); + + if let Err(_) = fs::rename(&src, &dest) { + if src.is_file() { + fs::copy(&src, &dest)?; + } else { + if entry.path().is_dir() { + fs::rename(&src, &dest)?; + } + } + } + } + + let _ = fs::remove_dir_all(&staging_dir); + return Ok(()); + } + } + } + + log!("Extracting directly (no single root folder detected)."); + let _ = fs::remove_dir_all(target_dir); + fs::rename(&staging_dir, target_dir)?; + + Ok(()) +} + +pub async fn download_and_extract_zip(url: &str, as_name: &str) { + let base_dir = PathBuf::from(get_directory()); + let zip_filename = format!("{}.zip", Uuid::new_v4()); // Use a unique name for the downloaded ZIP file + let zip_path = base_dir.join(&zip_filename); + let target_dir = base_dir.join(as_name); + + // Step 1: Download the ZIP file + if let Err(e) = download_zip(url, &zip_path).await { + log!("Error downloading file: {}", e); + return; + } + + // Step 2: Extract and flatten the ZIP file contents into the target directory + if let Err(e) = extract_zip_contents_to_folder(&zip_path, &target_dir) { + log!("Error extracting ZIP file contents: {}", e); + } + + // Step 3: Clean up the downloaded ZIP file + if let Err(e) = fs::remove_file(&zip_path) { + log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs new file mode 100644 index 0000000..47173e9 --- /dev/null +++ b/src/util/logger.rs @@ -0,0 +1,135 @@ +use std::{ + fs::{self, OpenOptions}, + io::Write, + path::Path, + sync::{OnceLock, mpsc}, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; +static LOGGER: OnceLock> = OnceLock::new(); + +struct LogMessage { + timestamp_ms: u128, + sender: Option, + message: String, +} + +/// Initialize logger (call once) +pub fn startup() { + let (tx, rx) = mpsc::channel::(); + + LOGGER.set(tx).expect("Logger already initialized"); + + thread::spawn(move || { + // Prepare log directory + let log_dir = Path::new("logs"); + fs::create_dir_all(log_dir).expect("Failed to create log directory"); + + let start_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let path = log_dir.join(format!("log_{}.txt", start_ts)); + + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .expect("Failed to open log file"); + + // Dedicated logging loop + for msg in rx { + let timestamp_box = fixed_box(&msg.timestamp_ms.to_string(), 13); + + let sender_box = match msg.sender { + Some(id) => fixed_box(&format!("{}", id), 19), + None => fixed_box("", 19), + }; + + let line = format!("{} {} {}", timestamp_box, sender_box, msg.message); + + println!("{}", line); + let _ = writeln!(file, "{}", line); + } + }); +} +fn fixed_box(content: &str, width: usize) -> String { + let s = content.chars().take(width).collect::(); + let len = s.chars().count(); + if len < width { + let mut a = " ".repeat(width - len); + a.push_str(&s); + format!("[{}]", a) + } else { + s + } +} +/// Internal function (sync + async safe) +pub fn log_internal(sender: Option, message: String) { + if let Some(tx) = LOGGER.get() { + let _ = tx.send(LogMessage { + timestamp_ms: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + sender, + message, + }); + } else { + println!("{}", message); + } +} + +#[macro_export] +macro_rules! log { + ($($arg:tt)*) => { + $crate::util::logger::log_internal(None, format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! log_from { + ($sender:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), format!($($arg)*)) + }; +} +#[macro_export] +macro_rules! log_in { + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + format!("> {}", format!($($arg)*)) + ) + }; +} + +#[macro_export] +macro_rules! log_out { + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + format!("< {}", format!($($arg)*)) + ) + }; +} + +#[macro_export] +macro_rules! log_in_from { + ($sender:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal( + Some($sender), + format!("> {}", format!($($arg)*)) + ) + }; +} + +#[macro_export] +macro_rules! log_out_from { + ($sender:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal( + Some($sender), + format!("< {}", format!($($arg)*)) + ) + }; +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..1cd66a6 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,3 @@ +pub mod crypto_helper; +pub mod file_util; +pub mod logger; From ebd5822dd66294df09c508a03d0dad62a42b5568 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 5 Jan 2026 02:15:55 +0100 Subject: [PATCH 007/220] Connection With Omikron --- Cargo.lock | 10 ++ Cargo.toml | 1 + src/data/communication.rs | 18 ++- src/main.rs | 11 +- src/server/api.rs | 10 +- src/server/omikron_connection.rs | 237 ++++++++++++++----------------- src/server/server.rs | 11 +- src/server/socket.rs | 5 + src/sql/sql.rs | 172 +++++++++++++++++----- src/util/logger.rs | 170 ++++++++++++++++------ 10 files changed, 427 insertions(+), 218 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bf40cc..45d3a1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "Omega" version = "0.1.0" dependencies = [ "aes-gcm", + "ansi_term", "async-tungstenite", "axum", "base64 0.22.1", @@ -109,6 +110,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "arbitrary" version = "1.4.2" diff --git a/Cargo.toml b/Cargo.toml index a1cb5fa..1331a56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] aes-gcm = "*" +ansi_term = "0.12.1" async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } axum = "0.8.7" base64 = "0.22.1" diff --git a/src/data/communication.rs b/src/data/communication.rs index 2f8c689..7af1340 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -36,6 +36,8 @@ pub enum DataTypes { shared_secret, call_id, call_token, + untill, + enable, start_date, end_date, receiver_id, @@ -110,6 +112,8 @@ impl DataTypes { "sharedsecret" => DataTypes::shared_secret, "callid" => DataTypes::call_id, "calltoken" => DataTypes::call_token, + "untill" => DataTypes::untill, + "enable" => DataTypes::enable, "startdate" => DataTypes::start_date, "enddate" => DataTypes::end_date, "receiverid" => DataTypes::receiver_id, @@ -159,11 +163,14 @@ impl DataTypes { pub enum CommunicationType { error, error_invalid_user_id, + error_invalid_omikron_id, error_not_found, + error_not_authenticated, error_no_iota, error_invalid_challenge, error_invalid_secret, error_invalid_private_key, + error_invalid_public_key, error_no_user_id, error_no_call_id, error_invalid_call_id, @@ -210,6 +217,9 @@ pub enum CommunicationType { watch_stream, call_token, call_invite, + call_disconnect_user, + call_timeout_user, + call_set_anonymous_joining, end_call, function, update, @@ -223,14 +233,20 @@ impl CommunicationType { "watchstream" => CommunicationType::watch_stream, "calltoken" => CommunicationType::call_token, "callinvite" => CommunicationType::call_invite, + "calldisconnectuser" => CommunicationType::call_disconnect_user, + "calltimeoutuser" => CommunicationType::call_timeout_user, + "callsetanonymousjoining" => CommunicationType::call_set_anonymous_joining, "endcall" => CommunicationType::end_call, "function" => CommunicationType::function, "update" => CommunicationType::update, "createuser" => CommunicationType::create_user, "errorinvaliduserid" => CommunicationType::error_invalid_user_id, + "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, "errornotfound" => CommunicationType::error_not_found, + "errornotauthenticated" => CommunicationType::error_not_authenticated, "errornoiota" => CommunicationType::error_no_iota, "errorinvalidchallenge" => CommunicationType::error_invalid_challenge, + "errorinvalidpublickey" => CommunicationType::error_invalid_public_key, "errorinvalidsecret" => CommunicationType::error_invalid_secret, "errorinvalidprivatekey" => CommunicationType::error_invalid_private_key, "errornouserid" => CommunicationType::error_no_user_id, @@ -243,7 +259,7 @@ impl CommunicationType { "message" => CommunicationType::message, "messagesend" => CommunicationType::message_send, "messagelive" => CommunicationType::message_live, - "messageother_iota" => CommunicationType::message_other_iota, + "messageotheriota" => CommunicationType::message_other_iota, "messagechunk" => CommunicationType::message_chunk, "messagesget" => CommunicationType::messages_get, "changeconfirm" => CommunicationType::change_confirm, diff --git a/src/main.rs b/src/main.rs index 01b51b8..bf945e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,9 +25,9 @@ pub fn get_public_key() -> x448::PublicKey { async fn main() { dotenv().ok(); startup(); - log!("Started"); log_in!("Incoming messages"); log_out!("Outgoing messages"); + log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { log!("[FATAL] Database initialization failed: {}", e); @@ -35,9 +35,14 @@ async fn main() { "[FATAL] Please ensure the database is running and the .env file is configured correctly." ); return; + } else { + log!(" DB"); + } + if let Err(e) = print_users().await { + log!("[ERROR] Failed to print users: {}", e); + } else { + log!(" Users"); } - let _ = print_users().await; - log!(" DB"); server::server::start(9187).await; log!(" Server"); loop {} diff --git a/src/server/api.rs b/src/server/api.rs index db464a2..3b5fbcc 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -65,19 +65,17 @@ pub async fn handle( let id = path_parts[3].parse::().unwrap_or(0); if id == 0 { not_found() - } else if let Ok((omikron_id, public_key, ip_address)) = - get_omikron_by_id(id).await - { + } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { ( StatusCode::OK, "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address + id, public_key, ip_address ), ) } else if let Some(omikron_id) = get_omikron_for_iota(id).await { - if let Ok((omikron_id, public_key, ip_address)) = + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { ( @@ -95,7 +93,7 @@ pub async fn handle( get_by_id(id).await { if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { - if let Ok((omikron_id, public_key, ip_address)) = + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { ( diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 5aafd16..ea2fa85 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,8 +1,9 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::get_public_key; use crate::sql::sql::get_omikron_by_id; use crate::util::crypto_helper::encrypt; -use crate::{get_private_key, log_in_from, log_out_from}; +use crate::util::logger::PrintType; +use crate::{get_private_key, log_out}; +use crate::{get_public_key, log_in}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use futures::SinkExt; @@ -53,10 +54,17 @@ impl OmikronConnection { waiting_tasks: DashMap::new(), }) } - pub async fn send_message(&self, message: &CommunicationValue) { + pub async fn send_message(&self, cv: &CommunicationValue) { let mut sender = self.sender.write().await; - let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string())); - log_out_from!(*self.omikron_id.read().await, "{}", message_text); + let message_text = Message::Text(Utf8Bytes::from(cv.to_json().to_string())); + if !cv.is_type(CommunicationType::pong) { + log_out!( + *self.omikron_id.read().await, + PrintType::Omikron, + "{}", + message_text + ); + } sender.send(message_text).await.unwrap(); } pub async fn get_user_id(&self) -> i64 { @@ -76,155 +84,124 @@ impl OmikronConnection { return; } - log_in_from!(*self.omikron_id.read().await, "{}", message); + log_in!( + *self.omikron_id.read().await, + PrintType::Omikron, + "{}", + message + ); + if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { + let _ = task(self.clone(), cv.clone()); + return; + } + + // Handle identification if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { let omikron_id = cv .get_data(DataTypes::omikron) .unwrap_or(&JsonValue::Null) .as_i64() .unwrap_or(0); - if let Ok((_, public_key, _)) = get_omikron_by_id(omikron_id).await { - // Generate Challenge, encrypt it and send it to the omikron - *self.omikron_id.write().await = omikron_id; - *self.identified.write().await = true; + match get_omikron_by_id(omikron_id).await { + Ok((public_key, _)) => { + // Generate Challenge, encrypt it and send it to the omikron + *self.omikron_id.write().await = omikron_id; - let challenge_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); + let challenge_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); - *self.challenge.write().await = challenge_str.clone(); + *self.challenge.write().await = challenge_str.clone(); - let user_public_key_bytes = match STANDARD.decode(&public_key) { - Ok(bytes) => bytes, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_user_id, + let user_public_key_bytes = match STANDARD.decode(&public_key) { + Ok(bytes) => bytes, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_omikron_id, + ) + .await; + return; + } + }; + *self.pub_key.write().await = Some(user_public_key_bytes.clone()); + + let omikron_pub_key: PublicKey = + match PublicKey::from_bytes(&user_public_key_bytes) { + Some(key) => key, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + return; + } + }; + + let encrypted_challenge = + encrypt(get_private_key(), omikron_pub_key, &challenge_str) + .unwrap_or("".to_string()); + + let response = CommunicationValue::new(CommunicationType::challenge) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), ) - .await; - return; - } - }; - *self.pub_key.write().await = Some(user_public_key_bytes.clone()); + .add_data_str(DataTypes::challenge, encrypted_challenge) + .with_id(cv.get_id()); - let omikron_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) - { - Some(key) => key, - None => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_user_id, - ) - .await; - return; - } - }; - - let encrypted_challenge = - encrypt(get_private_key(), omikron_pub_key, &challenge_str) - .unwrap_or("".to_string()); - - let response = CommunicationValue::new(CommunicationType::challenge) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), + self.send_message(&response).await; + *self.identified.write().await = true; + return; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error_not_authenticated) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), ) - .add_data_str(DataTypes::challenge, encrypted_challenge) + .await; + + return; + } + } + } + + // Handle challenge response + if *self.identified.read().await + && !*self.challenged.read().await + && cv.is_type(CommunicationType::challenge_response) + { + let client_response = cv + .get_data(DataTypes::challenge) + .unwrap_or(&JsonValue::Null) + .as_str() + .unwrap_or(""); + let expected_challenge = self.challenge.read().await.clone(); + + if client_response == expected_challenge { + *self.challenged.write().await = true; + + let response = CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()); self.send_message(&response).await; - // prepare Challenge Response handling - self.waiting_tasks.insert( - cv.get_id(), - Box::new( - |selfc: Arc, cv: CommunicationValue| -> bool { - tokio::spawn(async move { - let client_challenge_response_b64 = - match cv.get_data(DataTypes::challenge) { - Some(data) => data.to_string(), - None => { - selfc - .send_error_response( - &cv.get_id(), - CommunicationType::error, - ) - .await; - return; - } - }; - - let challenge_response_bytes = - match STANDARD.decode(&client_challenge_response_b64) { - Ok(bytes) => bytes, - Err(_) => { - selfc - .send_error_response( - &cv.get_id(), - CommunicationType::error, - ) - .await; - return; - } - }; - - if challenge_response_bytes.len() < 12 { - selfc - .send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - - let client_response = cv - .get_data(DataTypes::challenge) - .unwrap_or(&JsonValue::Null) - .as_str() - .unwrap_or(""); - - let expected_challenge = selfc.challenge.read().await.clone(); - - if client_response != expected_challenge { - selfc - .send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await; - selfc.close().await; - return; - } - - *selfc.challenged.write().await = true; - - let response = CommunicationValue::new( - CommunicationType::identification_response, - ) - .with_id(cv.get_id()); - - selfc.send_message(&response).await; - return; - }); - return true; - }, - ), - ); } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_challenge) .await; + self.close().await; } return; } - if self.waiting_tasks.contains_key(&cv.get_id()) { - let (_, task) = self.waiting_tasks.remove(&cv.get_id()).unwrap(); - let _ = task(self.clone(), cv.clone()); - } - if !self.is_identified().await { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) .await; + self.close().await; return; } } diff --git a/src/server/server.rs b/src/server/server.rs index 7dc19ab..263ff88 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -464,10 +464,19 @@ pub async fn start_omikron_handler(connection: Arc) { conn_clone.handle_message(text.to_string()).await; }); } + Message::Ping(_) => { + let pong_response = crate::data::communication::CommunicationValue::new( + crate::data::communication::CommunicationType::pong, + ); + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.send_message(&pong_response).await; + }); + } Message::Close(_) => { break; } - // Other message types like Binary, Ping, Pong are ignored. + // Other message types like Binary, Pong are ignored. _ => {} } } diff --git a/src/server/socket.rs b/src/server/socket.rs index 2ff5966..6cc825d 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -35,6 +35,11 @@ pub fn handle( .clone() .handle_message(text.to_string()) .await; + } else if msg.is_ping() { + let pong_response = crate::data::communication::CommunicationValue::new( + crate::data::communication::CommunicationType::pong, + ); + community_conn.send_message(&pong_response).await; } else if msg.is_close() { log!("Closing: {}", msg); community_conn.handle_close().await; diff --git a/src/sql/sql.rs b/src/sql/sql.rs index de749fa..a5463d7 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -1,6 +1,6 @@ use crate::log; use once_cell::sync::Lazy; -use sqlx::{MySql, Pool, mysql::MySqlPoolOptions}; +use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; use std::sync::atomic::{AtomicU64, Ordering}; use std::{ env, @@ -78,7 +78,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS iotas ( id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - public_key TEXT NOT NULL COLLATE utf8mb4_bin + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", ) .execute(&pool) @@ -88,9 +88,9 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { omikrons ( id BIGINT UNSIGNED NOT NULL PRIMARY KEY, is_active INT(1) NOT NULL DEFAULT 0, - public_key TEXT NOT NULL COLLATE utf8mb4_bin, - location TEXT NOT NULL COLLATE utf8mb4_bin, - ip_address TEXT NOT NULL COLLATE utf8mb4_bin + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", ) .execute(&pool) @@ -165,12 +165,45 @@ pub async fn get_by_username( let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( - "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", ) .bind(username) .fetch_optional(pool) - .await?.ok_or_else(|| sqlx::Error::RowNotFound) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let avatar: Vec = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + String::from_utf8_lossy(&display).to_string(), + String::from_utf8_lossy(&status).to_string(), + String::from_utf8_lossy(&about).to_string(), + String::from_utf8_lossy(&avatar).to_string(), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + None => Err(sqlx::Error::RowNotFound), + } } pub async fn get_by_id( @@ -195,12 +228,45 @@ pub async fn get_by_id( let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( - "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", ) .bind(id) .fetch_optional(pool) - .await?.ok_or_else(|| sqlx::Error::RowNotFound) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let avatar: Vec = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + String::from_utf8_lossy(&display).to_string(), + String::from_utf8_lossy(&status).to_string(), + String::from_utf8_lossy(&about).to_string(), + String::from_utf8_lossy(&avatar).to_string(), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + None => Err(sqlx::Error::RowNotFound), + } } pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { @@ -216,12 +282,12 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: Ok(()) } -pub async fn change_display_name(id: i64, new_display_name: String) -> Result<(), sqlx::Error> { +pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET display_name = ? WHERE id = ?") - .bind(new_display_name) + sqlx::query("UPDATE users SET display = ? WHERE id = ?") + .bind(new_display) .bind(id) .execute(pool) .await?; @@ -304,18 +370,18 @@ pub async fn register_complete_user( public_key: String, private_key_hash: String, iota_id: i64, - reset_token: String, + token: String, ) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, reset_token) VALUES (?, ?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, token) VALUES (?, ?, ?, ?, ?, ?)") .bind(id) .bind(username) .bind(public_key) .bind(private_key_hash) .bind(iota_id) - .bind(reset_token) + .bind(token) .execute(pool) .await?; @@ -326,17 +392,40 @@ pub async fn print_users() -> Result<(), Box> { let pool = db_lock.as_ref().expect("Database pool is not initialized"); log!("Printing users..."); - for (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) - in sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( - "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users", + for row in sqlx::query( + "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", ) .fetch_all(pool) .await? .iter() { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + log!( "User: {:?}", - (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) + ( + id, + iota_id, + username, + String::from_utf8_lossy(&display), + String::from_utf8_lossy(&status), + String::from_utf8_lossy(&about), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token) + ) ); } @@ -375,10 +464,11 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, String)>("SELECT id, public_key FROM iotas WHERE id = ?") + sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") .bind(id) .fetch_optional(pool) .await? + .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) .ok_or_else(|| sqlx::Error::RowNotFound) } @@ -414,24 +504,38 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - let a = sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") + let row = sqlx::query_as::<_, (i64, Vec, Vec)>("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") .fetch_optional(pool) - .await? - .ok_or_else(|| sqlx::Error::RowNotFound); - // this log isn't printing - log!("Random Omikron: {:?}", a); - a + .await?; + + match row { + Some((id, public_key, ip_address)) => Ok(( + id, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&ip_address).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + } } -pub async fn get_omikron_by_id(id: i64) -> Result<(i64, String, String), sqlx::Error> { +pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE id = ?") - .bind(id) - .fetch_optional(pool) - .await? - .ok_or_else(|| sqlx::Error::RowNotFound) + let row = sqlx::query_as::<_, (Vec, Vec)>( + "SELECT public_key, ip_address FROM omikrons WHERE id = ?", + ) + .bind(id) + .fetch_optional(pool) + .await?; + + match row { + Some((public_key, ip_address)) => Ok(( + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&ip_address).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + } } pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error> { diff --git a/src/util/logger.rs b/src/util/logger.rs index 47173e9..dede826 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -6,22 +6,37 @@ use std::{ thread, time::{SystemTime, UNIX_EPOCH}, }; + +use ansi_term::Color; + static LOGGER: OnceLock> = OnceLock::new(); +#[derive(Clone, Copy)] +pub enum PrintType { + Call, + Client, + Iota, + Omikron, + Omega, + General, +} + struct LogMessage { timestamp_ms: u128, sender: Option, + prefix: &'static str, + kind: PrintType, + is_error: bool, message: String, } -/// Initialize logger (call once) +/// Initialize the logging subsystem. +/// Must be called exactly once during startup. pub fn startup() { let (tx, rx) = mpsc::channel::(); - LOGGER.set(tx).expect("Logger already initialized"); thread::spawn(move || { - // Prepare log directory let log_dir = Path::new("logs"); fs::create_dir_all(log_dir).expect("Failed to create log directory"); @@ -31,42 +46,65 @@ pub fn startup() { .as_secs(); let path = log_dir.join(format!("log_{}.txt", start_ts)); - let mut file = OpenOptions::new() .create(true) .append(true) .open(path) .expect("Failed to open log file"); - // Dedicated logging loop for msg in rx { - let timestamp_box = fixed_box(&msg.timestamp_ms.to_string(), 13); - - let sender_box = match msg.sender { - Some(id) => fixed_box(&format!("{}", id), 19), + let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); + let sender = match msg.sender { + Some(id) => fixed_box(&id.to_string(), 19), None => fixed_box("", 19), }; - let line = format!("{} {} {}", timestamp_box, sender_box, msg.message); + let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); - println!("{}", line); + // Console (ANSI-colored) + println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); + + // File (plain text) let _ = writeln!(file, "{}", line); } }); } + +fn colorize(kind: PrintType, is_error: bool) -> Color { + if is_error { + return Color::Red; + } + + match kind { + PrintType::Call => Color::Purple, + PrintType::Client => Color::Green, + PrintType::Iota => Color::Yellow, + PrintType::Omikron => Color::Blue, + PrintType::Omega => Color::Cyan, + PrintType::General => Color::White, + } +} + fn fixed_box(content: &str, width: usize) -> String { - let s = content.chars().take(width).collect::(); + let s: String = content.chars().take(width).collect(); let len = s.chars().count(); if len < width { - let mut a = " ".repeat(width - len); - a.push_str(&s); - format!("[{}]", a) + format!("[{}{}]", " ".repeat(width - len), s) } else { s } } -/// Internal function (sync + async safe) -pub fn log_internal(sender: Option, message: String) { + +/** Internal async logging entry point. +* Not exposed publicly; all access goes through macros. +*/ +pub fn log_internal( + sender: Option, + kind: PrintType, + prefix: &'static str, + is_error: bool, + message: String, +) { if let Some(tx) = LOGGER.get() { let _ = tx.send(LogMessage { timestamp_ms: SystemTime::now() @@ -74,62 +112,108 @@ pub fn log_internal(sender: Option, message: String) { .unwrap() .as_millis(), sender, + prefix, + kind, + is_error, message, }); - } else { - println!("{}", message); } } - +/// Log a general informational message. #[macro_export] macro_rules! log { + // plain ($($arg:tt)*) => { - $crate::util::logger::log_internal(None, format!($($arg)*)) + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + "", + false, + format!($($arg)*) + ) }; -} -#[macro_export] -macro_rules! log_from { - ($sender:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), format!($($arg)*)) + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) }; } +/// Log an inbound message (`>`). #[macro_export] macro_rules! log_in { + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) + }; + + // plain ($($arg:tt)*) => { $crate::util::logger::log_internal( None, - format!("> {}", format!($($arg)*)) + $crate::util::logger::PrintType::General, + ">", + false, + format!($($arg)*) ) }; } - +/// Log an outbound message (`<`). #[macro_export] macro_rules! log_out { + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) + }; + + // plain ($($arg:tt)*) => { $crate::util::logger::log_internal( None, - format!("< {}", format!($($arg)*)) + $crate::util::logger::PrintType::General, + "<", + false, + format!($($arg)*) ) }; } - +/// Log an error message (`>>`). #[macro_export] -macro_rules! log_in_from { - ($sender:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal( - Some($sender), - format!("> {}", format!($($arg)*)) - ) - }; -} +macro_rules! log_err { -#[macro_export] -macro_rules! log_out_from { - ($sender:expr, $($arg:tt)*) => { + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { $crate::util::logger::log_internal( - Some($sender), - format!("< {}", format!($($arg)*)) + None, + $crate::util::logger::PrintType::General, + ">>", + true, + format!($($arg)*) ) }; } From 0122d5b3425b0f8b746bec3bc12df5c1e669e9e3 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 5 Jan 2026 02:15:55 +0100 Subject: [PATCH 008/220] Connection With Omikron --- Cargo.lock | 10 ++ Cargo.toml | 1 + src/data/communication.rs | 18 ++- src/main.rs | 11 +- src/server/api.rs | 10 +- src/server/omikron_connection.rs | 237 ++++++++++++++----------------- src/server/server.rs | 11 +- src/server/socket.rs | 5 + src/sql/sql.rs | 172 +++++++++++++++++----- src/util/logger.rs | 170 ++++++++++++++++------ 10 files changed, 427 insertions(+), 218 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bf40cc..45d3a1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7,6 +7,7 @@ name = "Omega" version = "0.1.0" dependencies = [ "aes-gcm", + "ansi_term", "async-tungstenite", "axum", "base64 0.22.1", @@ -109,6 +110,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "arbitrary" version = "1.4.2" diff --git a/Cargo.toml b/Cargo.toml index a1cb5fa..1331a56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] aes-gcm = "*" +ansi_term = "0.12.1" async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } axum = "0.8.7" base64 = "0.22.1" diff --git a/src/data/communication.rs b/src/data/communication.rs index 2f8c689..7af1340 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -36,6 +36,8 @@ pub enum DataTypes { shared_secret, call_id, call_token, + untill, + enable, start_date, end_date, receiver_id, @@ -110,6 +112,8 @@ impl DataTypes { "sharedsecret" => DataTypes::shared_secret, "callid" => DataTypes::call_id, "calltoken" => DataTypes::call_token, + "untill" => DataTypes::untill, + "enable" => DataTypes::enable, "startdate" => DataTypes::start_date, "enddate" => DataTypes::end_date, "receiverid" => DataTypes::receiver_id, @@ -159,11 +163,14 @@ impl DataTypes { pub enum CommunicationType { error, error_invalid_user_id, + error_invalid_omikron_id, error_not_found, + error_not_authenticated, error_no_iota, error_invalid_challenge, error_invalid_secret, error_invalid_private_key, + error_invalid_public_key, error_no_user_id, error_no_call_id, error_invalid_call_id, @@ -210,6 +217,9 @@ pub enum CommunicationType { watch_stream, call_token, call_invite, + call_disconnect_user, + call_timeout_user, + call_set_anonymous_joining, end_call, function, update, @@ -223,14 +233,20 @@ impl CommunicationType { "watchstream" => CommunicationType::watch_stream, "calltoken" => CommunicationType::call_token, "callinvite" => CommunicationType::call_invite, + "calldisconnectuser" => CommunicationType::call_disconnect_user, + "calltimeoutuser" => CommunicationType::call_timeout_user, + "callsetanonymousjoining" => CommunicationType::call_set_anonymous_joining, "endcall" => CommunicationType::end_call, "function" => CommunicationType::function, "update" => CommunicationType::update, "createuser" => CommunicationType::create_user, "errorinvaliduserid" => CommunicationType::error_invalid_user_id, + "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, "errornotfound" => CommunicationType::error_not_found, + "errornotauthenticated" => CommunicationType::error_not_authenticated, "errornoiota" => CommunicationType::error_no_iota, "errorinvalidchallenge" => CommunicationType::error_invalid_challenge, + "errorinvalidpublickey" => CommunicationType::error_invalid_public_key, "errorinvalidsecret" => CommunicationType::error_invalid_secret, "errorinvalidprivatekey" => CommunicationType::error_invalid_private_key, "errornouserid" => CommunicationType::error_no_user_id, @@ -243,7 +259,7 @@ impl CommunicationType { "message" => CommunicationType::message, "messagesend" => CommunicationType::message_send, "messagelive" => CommunicationType::message_live, - "messageother_iota" => CommunicationType::message_other_iota, + "messageotheriota" => CommunicationType::message_other_iota, "messagechunk" => CommunicationType::message_chunk, "messagesget" => CommunicationType::messages_get, "changeconfirm" => CommunicationType::change_confirm, diff --git a/src/main.rs b/src/main.rs index 01b51b8..bf945e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,9 +25,9 @@ pub fn get_public_key() -> x448::PublicKey { async fn main() { dotenv().ok(); startup(); - log!("Started"); log_in!("Incoming messages"); log_out!("Outgoing messages"); + log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { log!("[FATAL] Database initialization failed: {}", e); @@ -35,9 +35,14 @@ async fn main() { "[FATAL] Please ensure the database is running and the .env file is configured correctly." ); return; + } else { + log!(" DB"); + } + if let Err(e) = print_users().await { + log!("[ERROR] Failed to print users: {}", e); + } else { + log!(" Users"); } - let _ = print_users().await; - log!(" DB"); server::server::start(9187).await; log!(" Server"); loop {} diff --git a/src/server/api.rs b/src/server/api.rs index db464a2..3b5fbcc 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -65,19 +65,17 @@ pub async fn handle( let id = path_parts[3].parse::().unwrap_or(0); if id == 0 { not_found() - } else if let Ok((omikron_id, public_key, ip_address)) = - get_omikron_by_id(id).await - { + } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { ( StatusCode::OK, "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address + id, public_key, ip_address ), ) } else if let Some(omikron_id) = get_omikron_for_iota(id).await { - if let Ok((omikron_id, public_key, ip_address)) = + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { ( @@ -95,7 +93,7 @@ pub async fn handle( get_by_id(id).await { if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { - if let Ok((omikron_id, public_key, ip_address)) = + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { ( diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 5aafd16..ea2fa85 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,8 +1,9 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::get_public_key; use crate::sql::sql::get_omikron_by_id; use crate::util::crypto_helper::encrypt; -use crate::{get_private_key, log_in_from, log_out_from}; +use crate::util::logger::PrintType; +use crate::{get_private_key, log_out}; +use crate::{get_public_key, log_in}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use futures::SinkExt; @@ -53,10 +54,17 @@ impl OmikronConnection { waiting_tasks: DashMap::new(), }) } - pub async fn send_message(&self, message: &CommunicationValue) { + pub async fn send_message(&self, cv: &CommunicationValue) { let mut sender = self.sender.write().await; - let message_text = Message::Text(Utf8Bytes::from(message.to_json().to_string())); - log_out_from!(*self.omikron_id.read().await, "{}", message_text); + let message_text = Message::Text(Utf8Bytes::from(cv.to_json().to_string())); + if !cv.is_type(CommunicationType::pong) { + log_out!( + *self.omikron_id.read().await, + PrintType::Omikron, + "{}", + message_text + ); + } sender.send(message_text).await.unwrap(); } pub async fn get_user_id(&self) -> i64 { @@ -76,155 +84,124 @@ impl OmikronConnection { return; } - log_in_from!(*self.omikron_id.read().await, "{}", message); + log_in!( + *self.omikron_id.read().await, + PrintType::Omikron, + "{}", + message + ); + if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { + let _ = task(self.clone(), cv.clone()); + return; + } + + // Handle identification if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { let omikron_id = cv .get_data(DataTypes::omikron) .unwrap_or(&JsonValue::Null) .as_i64() .unwrap_or(0); - if let Ok((_, public_key, _)) = get_omikron_by_id(omikron_id).await { - // Generate Challenge, encrypt it and send it to the omikron - *self.omikron_id.write().await = omikron_id; - *self.identified.write().await = true; + match get_omikron_by_id(omikron_id).await { + Ok((public_key, _)) => { + // Generate Challenge, encrypt it and send it to the omikron + *self.omikron_id.write().await = omikron_id; - let challenge_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); + let challenge_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); - *self.challenge.write().await = challenge_str.clone(); + *self.challenge.write().await = challenge_str.clone(); - let user_public_key_bytes = match STANDARD.decode(&public_key) { - Ok(bytes) => bytes, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_user_id, + let user_public_key_bytes = match STANDARD.decode(&public_key) { + Ok(bytes) => bytes, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_omikron_id, + ) + .await; + return; + } + }; + *self.pub_key.write().await = Some(user_public_key_bytes.clone()); + + let omikron_pub_key: PublicKey = + match PublicKey::from_bytes(&user_public_key_bytes) { + Some(key) => key, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + return; + } + }; + + let encrypted_challenge = + encrypt(get_private_key(), omikron_pub_key, &challenge_str) + .unwrap_or("".to_string()); + + let response = CommunicationValue::new(CommunicationType::challenge) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), ) - .await; - return; - } - }; - *self.pub_key.write().await = Some(user_public_key_bytes.clone()); + .add_data_str(DataTypes::challenge, encrypted_challenge) + .with_id(cv.get_id()); - let omikron_pub_key: PublicKey = match PublicKey::from_bytes(&user_public_key_bytes) - { - Some(key) => key, - None => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_user_id, - ) - .await; - return; - } - }; - - let encrypted_challenge = - encrypt(get_private_key(), omikron_pub_key, &challenge_str) - .unwrap_or("".to_string()); - - let response = CommunicationValue::new(CommunicationType::challenge) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), + self.send_message(&response).await; + *self.identified.write().await = true; + return; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error_not_authenticated) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), ) - .add_data_str(DataTypes::challenge, encrypted_challenge) + .await; + + return; + } + } + } + + // Handle challenge response + if *self.identified.read().await + && !*self.challenged.read().await + && cv.is_type(CommunicationType::challenge_response) + { + let client_response = cv + .get_data(DataTypes::challenge) + .unwrap_or(&JsonValue::Null) + .as_str() + .unwrap_or(""); + let expected_challenge = self.challenge.read().await.clone(); + + if client_response == expected_challenge { + *self.challenged.write().await = true; + + let response = CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()); self.send_message(&response).await; - // prepare Challenge Response handling - self.waiting_tasks.insert( - cv.get_id(), - Box::new( - |selfc: Arc, cv: CommunicationValue| -> bool { - tokio::spawn(async move { - let client_challenge_response_b64 = - match cv.get_data(DataTypes::challenge) { - Some(data) => data.to_string(), - None => { - selfc - .send_error_response( - &cv.get_id(), - CommunicationType::error, - ) - .await; - return; - } - }; - - let challenge_response_bytes = - match STANDARD.decode(&client_challenge_response_b64) { - Ok(bytes) => bytes, - Err(_) => { - selfc - .send_error_response( - &cv.get_id(), - CommunicationType::error, - ) - .await; - return; - } - }; - - if challenge_response_bytes.len() < 12 { - selfc - .send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - - let client_response = cv - .get_data(DataTypes::challenge) - .unwrap_or(&JsonValue::Null) - .as_str() - .unwrap_or(""); - - let expected_challenge = selfc.challenge.read().await.clone(); - - if client_response != expected_challenge { - selfc - .send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await; - selfc.close().await; - return; - } - - *selfc.challenged.write().await = true; - - let response = CommunicationValue::new( - CommunicationType::identification_response, - ) - .with_id(cv.get_id()); - - selfc.send_message(&response).await; - return; - }); - return true; - }, - ), - ); } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_challenge) .await; + self.close().await; } return; } - if self.waiting_tasks.contains_key(&cv.get_id()) { - let (_, task) = self.waiting_tasks.remove(&cv.get_id()).unwrap(); - let _ = task(self.clone(), cv.clone()); - } - if !self.is_identified().await { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) .await; + self.close().await; return; } } diff --git a/src/server/server.rs b/src/server/server.rs index 7dc19ab..263ff88 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -464,10 +464,19 @@ pub async fn start_omikron_handler(connection: Arc) { conn_clone.handle_message(text.to_string()).await; }); } + Message::Ping(_) => { + let pong_response = crate::data::communication::CommunicationValue::new( + crate::data::communication::CommunicationType::pong, + ); + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.send_message(&pong_response).await; + }); + } Message::Close(_) => { break; } - // Other message types like Binary, Ping, Pong are ignored. + // Other message types like Binary, Pong are ignored. _ => {} } } diff --git a/src/server/socket.rs b/src/server/socket.rs index 2ff5966..6cc825d 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -35,6 +35,11 @@ pub fn handle( .clone() .handle_message(text.to_string()) .await; + } else if msg.is_ping() { + let pong_response = crate::data::communication::CommunicationValue::new( + crate::data::communication::CommunicationType::pong, + ); + community_conn.send_message(&pong_response).await; } else if msg.is_close() { log!("Closing: {}", msg); community_conn.handle_close().await; diff --git a/src/sql/sql.rs b/src/sql/sql.rs index de749fa..a5463d7 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -1,6 +1,6 @@ use crate::log; use once_cell::sync::Lazy; -use sqlx::{MySql, Pool, mysql::MySqlPoolOptions}; +use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; use std::sync::atomic::{AtomicU64, Ordering}; use std::{ env, @@ -78,7 +78,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS iotas ( id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - public_key TEXT NOT NULL COLLATE utf8mb4_bin + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", ) .execute(&pool) @@ -88,9 +88,9 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { omikrons ( id BIGINT UNSIGNED NOT NULL PRIMARY KEY, is_active INT(1) NOT NULL DEFAULT 0, - public_key TEXT NOT NULL COLLATE utf8mb4_bin, - location TEXT NOT NULL COLLATE utf8mb4_bin, - ip_address TEXT NOT NULL COLLATE utf8mb4_bin + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", ) .execute(&pool) @@ -165,12 +165,45 @@ pub async fn get_by_username( let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( - "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", ) .bind(username) .fetch_optional(pool) - .await?.ok_or_else(|| sqlx::Error::RowNotFound) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let avatar: Vec = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + String::from_utf8_lossy(&display).to_string(), + String::from_utf8_lossy(&status).to_string(), + String::from_utf8_lossy(&about).to_string(), + String::from_utf8_lossy(&avatar).to_string(), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + None => Err(sqlx::Error::RowNotFound), + } } pub async fn get_by_id( @@ -195,12 +228,45 @@ pub async fn get_by_id( let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( - "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", ) .bind(id) .fetch_optional(pool) - .await?.ok_or_else(|| sqlx::Error::RowNotFound) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let avatar: Vec = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + String::from_utf8_lossy(&display).to_string(), + String::from_utf8_lossy(&status).to_string(), + String::from_utf8_lossy(&about).to_string(), + String::from_utf8_lossy(&avatar).to_string(), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + None => Err(sqlx::Error::RowNotFound), + } } pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { @@ -216,12 +282,12 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: Ok(()) } -pub async fn change_display_name(id: i64, new_display_name: String) -> Result<(), sqlx::Error> { +pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET display_name = ? WHERE id = ?") - .bind(new_display_name) + sqlx::query("UPDATE users SET display = ? WHERE id = ?") + .bind(new_display) .bind(id) .execute(pool) .await?; @@ -304,18 +370,18 @@ pub async fn register_complete_user( public_key: String, private_key_hash: String, iota_id: i64, - reset_token: String, + token: String, ) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, reset_token) VALUES (?, ?, ?, ?, ?, ?)") + sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, token) VALUES (?, ?, ?, ?, ?, ?)") .bind(id) .bind(username) .bind(public_key) .bind(private_key_hash) .bind(iota_id) - .bind(reset_token) + .bind(token) .execute(pool) .await?; @@ -326,17 +392,40 @@ pub async fn print_users() -> Result<(), Box> { let pool = db_lock.as_ref().expect("Database pool is not initialized"); log!("Printing users..."); - for (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) - in sqlx::query_as::<_, (i64, i64, String, String, String, String, String, i32, i64, String, String, String)>( - "SELECT id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users", + for row in sqlx::query( + "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", ) .fetch_all(pool) .await? .iter() { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + log!( "User: {:?}", - (id, iota_id, username, display_name, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token) + ( + id, + iota_id, + username, + String::from_utf8_lossy(&display), + String::from_utf8_lossy(&status), + String::from_utf8_lossy(&about), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token) + ) ); } @@ -375,10 +464,11 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, String)>("SELECT id, public_key FROM iotas WHERE id = ?") + sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") .bind(id) .fetch_optional(pool) .await? + .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) .ok_or_else(|| sqlx::Error::RowNotFound) } @@ -414,24 +504,38 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - let a = sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") + let row = sqlx::query_as::<_, (i64, Vec, Vec)>("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") .fetch_optional(pool) - .await? - .ok_or_else(|| sqlx::Error::RowNotFound); - // this log isn't printing - log!("Random Omikron: {:?}", a); - a + .await?; + + match row { + Some((id, public_key, ip_address)) => Ok(( + id, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&ip_address).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + } } -pub async fn get_omikron_by_id(id: i64) -> Result<(i64, String, String), sqlx::Error> { +pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as("SELECT id, public_key, ip_address FROM omikrons WHERE id = ?") - .bind(id) - .fetch_optional(pool) - .await? - .ok_or_else(|| sqlx::Error::RowNotFound) + let row = sqlx::query_as::<_, (Vec, Vec)>( + "SELECT public_key, ip_address FROM omikrons WHERE id = ?", + ) + .bind(id) + .fetch_optional(pool) + .await?; + + match row { + Some((public_key, ip_address)) => Ok(( + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&ip_address).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + } } pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error> { diff --git a/src/util/logger.rs b/src/util/logger.rs index 47173e9..dede826 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -6,22 +6,37 @@ use std::{ thread, time::{SystemTime, UNIX_EPOCH}, }; + +use ansi_term::Color; + static LOGGER: OnceLock> = OnceLock::new(); +#[derive(Clone, Copy)] +pub enum PrintType { + Call, + Client, + Iota, + Omikron, + Omega, + General, +} + struct LogMessage { timestamp_ms: u128, sender: Option, + prefix: &'static str, + kind: PrintType, + is_error: bool, message: String, } -/// Initialize logger (call once) +/// Initialize the logging subsystem. +/// Must be called exactly once during startup. pub fn startup() { let (tx, rx) = mpsc::channel::(); - LOGGER.set(tx).expect("Logger already initialized"); thread::spawn(move || { - // Prepare log directory let log_dir = Path::new("logs"); fs::create_dir_all(log_dir).expect("Failed to create log directory"); @@ -31,42 +46,65 @@ pub fn startup() { .as_secs(); let path = log_dir.join(format!("log_{}.txt", start_ts)); - let mut file = OpenOptions::new() .create(true) .append(true) .open(path) .expect("Failed to open log file"); - // Dedicated logging loop for msg in rx { - let timestamp_box = fixed_box(&msg.timestamp_ms.to_string(), 13); - - let sender_box = match msg.sender { - Some(id) => fixed_box(&format!("{}", id), 19), + let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); + let sender = match msg.sender { + Some(id) => fixed_box(&id.to_string(), 19), None => fixed_box("", 19), }; - let line = format!("{} {} {}", timestamp_box, sender_box, msg.message); + let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); - println!("{}", line); + // Console (ANSI-colored) + println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); + + // File (plain text) let _ = writeln!(file, "{}", line); } }); } + +fn colorize(kind: PrintType, is_error: bool) -> Color { + if is_error { + return Color::Red; + } + + match kind { + PrintType::Call => Color::Purple, + PrintType::Client => Color::Green, + PrintType::Iota => Color::Yellow, + PrintType::Omikron => Color::Blue, + PrintType::Omega => Color::Cyan, + PrintType::General => Color::White, + } +} + fn fixed_box(content: &str, width: usize) -> String { - let s = content.chars().take(width).collect::(); + let s: String = content.chars().take(width).collect(); let len = s.chars().count(); if len < width { - let mut a = " ".repeat(width - len); - a.push_str(&s); - format!("[{}]", a) + format!("[{}{}]", " ".repeat(width - len), s) } else { s } } -/// Internal function (sync + async safe) -pub fn log_internal(sender: Option, message: String) { + +/** Internal async logging entry point. +* Not exposed publicly; all access goes through macros. +*/ +pub fn log_internal( + sender: Option, + kind: PrintType, + prefix: &'static str, + is_error: bool, + message: String, +) { if let Some(tx) = LOGGER.get() { let _ = tx.send(LogMessage { timestamp_ms: SystemTime::now() @@ -74,62 +112,108 @@ pub fn log_internal(sender: Option, message: String) { .unwrap() .as_millis(), sender, + prefix, + kind, + is_error, message, }); - } else { - println!("{}", message); } } - +/// Log a general informational message. #[macro_export] macro_rules! log { + // plain ($($arg:tt)*) => { - $crate::util::logger::log_internal(None, format!($($arg)*)) + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + "", + false, + format!($($arg)*) + ) }; -} -#[macro_export] -macro_rules! log_from { - ($sender:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), format!($($arg)*)) + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) }; } +/// Log an inbound message (`>`). #[macro_export] macro_rules! log_in { + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) + }; + + // plain ($($arg:tt)*) => { $crate::util::logger::log_internal( None, - format!("> {}", format!($($arg)*)) + $crate::util::logger::PrintType::General, + ">", + false, + format!($($arg)*) ) }; } - +/// Log an outbound message (`<`). #[macro_export] macro_rules! log_out { + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) + }; + + // plain ($($arg:tt)*) => { $crate::util::logger::log_internal( None, - format!("< {}", format!($($arg)*)) + $crate::util::logger::PrintType::General, + "<", + false, + format!($($arg)*) ) }; } - +/// Log an error message (`>>`). #[macro_export] -macro_rules! log_in_from { - ($sender:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal( - Some($sender), - format!("> {}", format!($($arg)*)) - ) - }; -} +macro_rules! log_err { -#[macro_export] -macro_rules! log_out_from { - ($sender:expr, $($arg:tt)*) => { + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { $crate::util::logger::log_internal( - Some($sender), - format!("< {}", format!($($arg)*)) + None, + $crate::util::logger::PrintType::General, + ">>", + true, + format!($($arg)*) ) }; } From 8d964bd9e5e427a271a0946dc2648d738b79b269 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 6 Jan 2026 22:16:53 +0100 Subject: [PATCH 009/220] Omikron Connection --- src/data/communication.rs | 24 +++++++++---- src/server/omikron_connection.rs | 59 +++++++++++++++++++++++++++++++- src/sql/iota_omikron_tracker.rs | 5 +++ src/sql/mod.rs | 1 + src/sql/user_online_tracker.rs | 27 +++++++++++++++ 5 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 src/sql/user_online_tracker.rs diff --git a/src/data/communication.rs b/src/data/communication.rs index 7af1340..58fd957 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -16,6 +16,7 @@ pub enum DataTypes { iota_id, user_id, user_ids, + iota_ids, user_state, user_states, user_pings, @@ -75,7 +76,7 @@ pub enum DataTypes { challenge, community_title, communities, - + rho_connections, user, } @@ -92,6 +93,7 @@ impl DataTypes { "iotaid" => DataTypes::iota_id, "userid" => DataTypes::user_id, "userids" => DataTypes::user_ids, + "iotaids" => DataTypes::iota_ids, "userstate" => DataTypes::user_state, "userstates" => DataTypes::user_states, "userpings" => DataTypes::user_pings, @@ -151,7 +153,7 @@ impl DataTypes { "challenge" => DataTypes::challenge, "communitytitle" => DataTypes::community_title, "communities" => DataTypes::communities, - + "rhoconnections" => DataTypes::rho_connections, "user" => DataTypes::user, _ => DataTypes::error_type, // fallback if unknown } @@ -202,8 +204,6 @@ pub enum CommunicationType { pong, add_chat, send_chat, - iota_connected, - iota_closed, client_changed, client_connected, client_disconnected, @@ -224,6 +224,13 @@ pub enum CommunicationType { function, update, create_user, + rho_update, + + user_connected, + user_disconnected, + iota_connected, + iota_disconnected, + sync_client_iota_status, } impl CommunicationType { pub fn parse(p0: String) -> CommunicationType { @@ -280,8 +287,6 @@ impl CommunicationType { "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, "clientdisconnected" => CommunicationType::client_disconnected, @@ -292,6 +297,13 @@ impl CommunicationType { "webrtcice" => CommunicationType::webrtc_ice, "startstream" => CommunicationType::start_stream, "endstream" => CommunicationType::end_stream, + "rhoupdate" => CommunicationType::rho_update, + + "iotaconnected" => CommunicationType::iota_connected, + "iotadisconnected" => CommunicationType::iota_disconnected, + "userconnected" => CommunicationType::user_connected, + "userdisconnected" => CommunicationType::user_disconnected, + "syncclientiotastatus" => CommunicationType::sync_client_iota_status, _ => CommunicationType::error, } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index ea2fa85..d9ba663 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,5 +1,11 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::sql::iota_omikron_tracker::{ + track_iota_omikron, untrack_by_omikron as untrack_iota_by_omikron, untrack_iota, +}; use crate::sql::sql::get_omikron_by_id; +use crate::sql::user_online_tracker::{ + track_user_omikron, untrack_by_omikron as untrack_user_by_omikron, untrack_user, +}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; use crate::{get_private_key, log_out}; @@ -204,6 +210,53 @@ impl OmikronConnection { self.close().await; return; } + + let omikron_id = self.get_user_id().await; + if cv.is_type(CommunicationType::user_connected) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + track_user_omikron(user_id, omikron_id).await; + } + return; + } + if cv.is_type(CommunicationType::user_disconnected) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + untrack_user(user_id).await; + } + return; + } + if cv.is_type(CommunicationType::iota_connected) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { + track_iota_omikron(iota_id, omikron_id).await; + } + return; + } + if cv.is_type(CommunicationType::iota_disconnected) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { + untrack_iota(iota_id).await; + } + return; + } + if cv.is_type(CommunicationType::sync_client_iota_status) { + if let Some(json::JsonValue::Array(user_ids)) = + cv.get_data(DataTypes::user_ids).cloned() + { + for user_id_json in user_ids { + if let Some(user_id) = user_id_json.as_i64() { + track_user_omikron(user_id, omikron_id).await; + } + } + } + if let Some(json::JsonValue::Array(iota_ids)) = + cv.get_data(DataTypes::iota_ids).cloned() + { + for iota_id_json in iota_ids { + if let Some(iota_id) = iota_id_json.as_i64() { + track_iota_omikron(iota_id, omikron_id).await; + } + } + } + return; + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { @@ -216,7 +269,11 @@ impl OmikronConnection { } pub async fn handle_close(self: Arc) { if self.is_identified().await { - if self.get_user_id().await != 0 {} + let omikron_id = self.get_user_id().await; + if omikron_id != 0 { + untrack_iota_by_omikron(omikron_id).await; + untrack_user_by_omikron(omikron_id).await; + } } } diff --git a/src/sql/iota_omikron_tracker.rs b/src/sql/iota_omikron_tracker.rs index 7d768ca..cf11310 100644 --- a/src/sql/iota_omikron_tracker.rs +++ b/src/sql/iota_omikron_tracker.rs @@ -20,3 +20,8 @@ pub async fn untrack_iota(iota: i64) { let mut c = IOTA_OMIKRON_MAP.write().await; c.remove(&iota); } + +pub async fn untrack_by_omikron(omikron: i64) { + let mut c = IOTA_OMIKRON_MAP.write().await; + c.retain(|_, v| *v != omikron); +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 9ef349a..583d27a 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,2 +1,3 @@ pub mod iota_omikron_tracker; pub mod sql; +pub mod user_online_tracker; diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs new file mode 100644 index 0000000..0cae3fa --- /dev/null +++ b/src/sql/user_online_tracker.rs @@ -0,0 +1,27 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +static USER_OMIKRON_MAP: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn track_user_omikron(user: i64, omikron: i64) { + let mut c = USER_OMIKRON_MAP.write().await; + c.insert(user, omikron); +} + +pub async fn get_omikron_for_user(user: i64) -> Option { + let c = USER_OMIKRON_MAP.read().await; + c.get(&user).cloned() +} + +pub async fn untrack_user(user: i64) { + let mut c = USER_OMIKRON_MAP.write().await; + c.remove(&user); +} + +pub async fn untrack_by_omikron(omikron: i64) { + let mut c = USER_OMIKRON_MAP.write().await; + c.retain(|_, v| *v != omikron); +} From 8ae9b94d8b4c55bb1cb28881daccfe6967e6dbbd Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 6 Jan 2026 22:16:53 +0100 Subject: [PATCH 010/220] Omikron Connection --- src/data/communication.rs | 24 +++++++++---- src/server/omikron_connection.rs | 59 +++++++++++++++++++++++++++++++- src/sql/iota_omikron_tracker.rs | 5 +++ src/sql/mod.rs | 1 + src/sql/user_online_tracker.rs | 27 +++++++++++++++ 5 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 src/sql/user_online_tracker.rs diff --git a/src/data/communication.rs b/src/data/communication.rs index 7af1340..58fd957 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -16,6 +16,7 @@ pub enum DataTypes { iota_id, user_id, user_ids, + iota_ids, user_state, user_states, user_pings, @@ -75,7 +76,7 @@ pub enum DataTypes { challenge, community_title, communities, - + rho_connections, user, } @@ -92,6 +93,7 @@ impl DataTypes { "iotaid" => DataTypes::iota_id, "userid" => DataTypes::user_id, "userids" => DataTypes::user_ids, + "iotaids" => DataTypes::iota_ids, "userstate" => DataTypes::user_state, "userstates" => DataTypes::user_states, "userpings" => DataTypes::user_pings, @@ -151,7 +153,7 @@ impl DataTypes { "challenge" => DataTypes::challenge, "communitytitle" => DataTypes::community_title, "communities" => DataTypes::communities, - + "rhoconnections" => DataTypes::rho_connections, "user" => DataTypes::user, _ => DataTypes::error_type, // fallback if unknown } @@ -202,8 +204,6 @@ pub enum CommunicationType { pong, add_chat, send_chat, - iota_connected, - iota_closed, client_changed, client_connected, client_disconnected, @@ -224,6 +224,13 @@ pub enum CommunicationType { function, update, create_user, + rho_update, + + user_connected, + user_disconnected, + iota_connected, + iota_disconnected, + sync_client_iota_status, } impl CommunicationType { pub fn parse(p0: String) -> CommunicationType { @@ -280,8 +287,6 @@ impl CommunicationType { "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, "clientdisconnected" => CommunicationType::client_disconnected, @@ -292,6 +297,13 @@ impl CommunicationType { "webrtcice" => CommunicationType::webrtc_ice, "startstream" => CommunicationType::start_stream, "endstream" => CommunicationType::end_stream, + "rhoupdate" => CommunicationType::rho_update, + + "iotaconnected" => CommunicationType::iota_connected, + "iotadisconnected" => CommunicationType::iota_disconnected, + "userconnected" => CommunicationType::user_connected, + "userdisconnected" => CommunicationType::user_disconnected, + "syncclientiotastatus" => CommunicationType::sync_client_iota_status, _ => CommunicationType::error, } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index ea2fa85..d9ba663 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,5 +1,11 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::sql::iota_omikron_tracker::{ + track_iota_omikron, untrack_by_omikron as untrack_iota_by_omikron, untrack_iota, +}; use crate::sql::sql::get_omikron_by_id; +use crate::sql::user_online_tracker::{ + track_user_omikron, untrack_by_omikron as untrack_user_by_omikron, untrack_user, +}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; use crate::{get_private_key, log_out}; @@ -204,6 +210,53 @@ impl OmikronConnection { self.close().await; return; } + + let omikron_id = self.get_user_id().await; + if cv.is_type(CommunicationType::user_connected) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + track_user_omikron(user_id, omikron_id).await; + } + return; + } + if cv.is_type(CommunicationType::user_disconnected) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + untrack_user(user_id).await; + } + return; + } + if cv.is_type(CommunicationType::iota_connected) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { + track_iota_omikron(iota_id, omikron_id).await; + } + return; + } + if cv.is_type(CommunicationType::iota_disconnected) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { + untrack_iota(iota_id).await; + } + return; + } + if cv.is_type(CommunicationType::sync_client_iota_status) { + if let Some(json::JsonValue::Array(user_ids)) = + cv.get_data(DataTypes::user_ids).cloned() + { + for user_id_json in user_ids { + if let Some(user_id) = user_id_json.as_i64() { + track_user_omikron(user_id, omikron_id).await; + } + } + } + if let Some(json::JsonValue::Array(iota_ids)) = + cv.get_data(DataTypes::iota_ids).cloned() + { + for iota_id_json in iota_ids { + if let Some(iota_id) = iota_id_json.as_i64() { + track_iota_omikron(iota_id, omikron_id).await; + } + } + } + return; + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { @@ -216,7 +269,11 @@ impl OmikronConnection { } pub async fn handle_close(self: Arc) { if self.is_identified().await { - if self.get_user_id().await != 0 {} + let omikron_id = self.get_user_id().await; + if omikron_id != 0 { + untrack_iota_by_omikron(omikron_id).await; + untrack_user_by_omikron(omikron_id).await; + } } } diff --git a/src/sql/iota_omikron_tracker.rs b/src/sql/iota_omikron_tracker.rs index 7d768ca..cf11310 100644 --- a/src/sql/iota_omikron_tracker.rs +++ b/src/sql/iota_omikron_tracker.rs @@ -20,3 +20,8 @@ pub async fn untrack_iota(iota: i64) { let mut c = IOTA_OMIKRON_MAP.write().await; c.remove(&iota); } + +pub async fn untrack_by_omikron(omikron: i64) { + let mut c = IOTA_OMIKRON_MAP.write().await; + c.retain(|_, v| *v != omikron); +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 9ef349a..583d27a 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,2 +1,3 @@ pub mod iota_omikron_tracker; pub mod sql; +pub mod user_online_tracker; diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs new file mode 100644 index 0000000..0cae3fa --- /dev/null +++ b/src/sql/user_online_tracker.rs @@ -0,0 +1,27 @@ +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; + +static USER_OMIKRON_MAP: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn track_user_omikron(user: i64, omikron: i64) { + let mut c = USER_OMIKRON_MAP.write().await; + c.insert(user, omikron); +} + +pub async fn get_omikron_for_user(user: i64) -> Option { + let c = USER_OMIKRON_MAP.read().await; + c.get(&user).cloned() +} + +pub async fn untrack_user(user: i64) { + let mut c = USER_OMIKRON_MAP.write().await; + c.remove(&user); +} + +pub async fn untrack_by_omikron(omikron: i64) { + let mut c = USER_OMIKRON_MAP.write().await; + c.retain(|_, v| *v != omikron); +} From 020bb7b072c7dd388bc39d0ee3662697bb59ce19 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:54:05 +0100 Subject: [PATCH 011/220] Format, Status, User Stati & Endpoints --- Cargo.lock | 260 +++++++++------- Cargo.toml | 1 + src/data/communication.rs | 24 ++ src/server/api.rs | 142 ++++++++- src/server/omikron_connection.rs | 511 +++++++++++++++++++++++++------ src/server/server.rs | 107 +------ src/server/socket.rs | 101 +++--- src/sql/connection_status.rs | 38 +++ src/sql/mod.rs | 1 + src/sql/sql.rs | 73 ++++- src/sql/user_online_tracker.rs | 99 +++++- 11 files changed, 962 insertions(+), 395 deletions(-) create mode 100644 src/sql/connection_status.rs diff --git a/Cargo.lock b/Cargo.lock index 45d3a1b..05b9215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "ansi_term", + "async-trait", "async-tungstenite", "axum", "base64 0.22.1", @@ -27,7 +28,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "reqwest", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pemfile 2.2.0", "sha1", "sha2", @@ -174,7 +175,7 @@ dependencies = [ "async-channel 2.5.0", "async-executor", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "blocking", "futures-lite 2.6.1", "once_cell", @@ -229,9 +230,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -259,7 +260,7 @@ dependencies = [ "async-channel 1.9.0", "async-global-executor", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "crossbeam-utils", "futures-channel", "futures-core", @@ -296,10 +297,21 @@ dependencies = [ ] [[package]] -name = "async-tungstenite" -version = "0.32.0" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-tungstenite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4" dependencies = [ "async-native-tls", "async-std", @@ -322,7 +334,7 @@ dependencies = [ "tokio-openssl", "tokio-rustls", "tungstenite", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -348,9 +360,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" dependencies = [ "aws-lc-sys", "zeroize", @@ -358,9 +370,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" dependencies = [ "cc", "cmake", @@ -370,9 +382,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -403,9 +415,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -449,9 +461,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "bitflags" @@ -492,9 +504,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -519,9 +531,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -531,9 +543,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" +checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720" dependencies = [ "smallvec", "target-lexicon", @@ -557,9 +569,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -920,9 +932,9 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "flate2" @@ -1167,9 +1179,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gio" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeff3dd716d1ba91850b976b76a1c2d28f99ef6c1602cd8fdaa8fab8017fd9c" +checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" dependencies = [ "futures-channel", "futures-core", @@ -1184,9 +1196,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ "glib-sys", "gobject-sys", @@ -1197,9 +1209,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ "bitflags 2.10.0", "futures-channel", @@ -1218,9 +1230,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ "heck", "proc-macro-crate", @@ -1231,9 +1243,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" dependencies = [ "libc", "system-deps", @@ -1259,9 +1271,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ "glib-sys", "libc", @@ -1270,9 +1282,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1447,7 +1459,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1660,9 +1672,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -1670,9 +1682,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -1726,9 +1738,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libm" @@ -1738,13 +1750,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.7.0", ] [[package]] @@ -1759,9 +1771,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" dependencies = [ "zlib-rs", ] @@ -1870,7 +1882,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -1886,9 +1898,9 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -2017,6 +2029,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -2313,18 +2331,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -2411,9 +2429,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -2503,9 +2521,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -2568,9 +2586,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -2583,11 +2601,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.0", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -2613,9 +2631,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "zeroize", ] @@ -2650,9 +2668,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -2756,15 +2774,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -2780,9 +2798,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3129,9 +3147,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -3214,9 +3232,9 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand 2.3.0", "getrandom 0.3.4", @@ -3320,9 +3338,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -3373,7 +3391,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.36", "tokio", ] @@ -3391,9 +3409,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -3408,9 +3426,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -3423,18 +3441,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -3444,18 +3462,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" @@ -3505,9 +3523,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3528,9 +3546,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3576,7 +3594,7 @@ dependencies = [ "log", "native-tls", "rand 0.9.2", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "sha1", "thiserror 2.0.17", @@ -3635,9 +3653,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -3833,9 +3851,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -4251,9 +4269,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -4306,18 +4324,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" dependencies = [ "proc-macro2", "quote", @@ -4356,9 +4374,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", @@ -4427,9 +4445,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 1331a56..d5e2dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] aes-gcm = "*" ansi_term = "0.12.1" +async-trait = "0.1.89" async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } axum = "0.8.7" base64 = "0.22.1" diff --git a/src/data/communication.rs b/src/data/communication.rs index 58fd957..e21dcae 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -78,6 +78,9 @@ pub enum DataTypes { communities, rho_connections, user, + online_status, + omikron_id, + omikron_connections, } impl DataTypes { @@ -155,6 +158,9 @@ impl DataTypes { "communities" => DataTypes::communities, "rhoconnections" => DataTypes::rho_connections, "user" => DataTypes::user, + "onlinestatus" => DataTypes::online_status, + "omikronid" => DataTypes::omikron_id, + "omikronconnections" => DataTypes::omikron_connections, _ => DataTypes::error_type, // fallback if unknown } } @@ -231,6 +237,15 @@ pub enum CommunicationType { iota_connected, iota_disconnected, sync_client_iota_status, + + get_user_data, + get_iota_data, + + change_user_data, + change_iota_data, + + start_register, + complete_register, } impl CommunicationType { pub fn parse(p0: String) -> CommunicationType { @@ -305,6 +320,15 @@ impl CommunicationType { "userdisconnected" => CommunicationType::user_disconnected, "syncclientiotastatus" => CommunicationType::sync_client_iota_status, + "getuserdata" => CommunicationType::get_user_data, + "getiotadata" => CommunicationType::get_iota_data, + + "changeuserdata" => CommunicationType::change_user_data, + "changeiotadata" => CommunicationType::change_iota_data, + + "startregister" => CommunicationType::start_register, + "completeregister" => CommunicationType::complete_register, + _ => CommunicationType::error, } } diff --git a/src/server/api.rs b/src/server/api.rs index 3b5fbcc..69077a4 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,13 +1,10 @@ -use crate::{get_public_key, log}; +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::get_public_key; +use crate::sql::sql; use crate::{ sql::{ - iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, - sql::{ - change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, - change_keys, change_status, change_username, get_by_id, get_by_username, - get_iota_by_id, get_omikron_by_id, get_random_omikron, get_register_id, - register_complete_iota, register_complete_user, - }, + iota_omikron_tracker::get_omikron_for_iota, + sql::{get_by_user_id, get_omikron_by_id, get_random_omikron}, }, util::crypto_helper::public_key_to_base64, }; @@ -16,6 +13,7 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use json::JsonValue; +use json::number::Number; pub async fn handle( path: &str, @@ -37,11 +35,6 @@ pub async fn handle( // get/ // omikron/ // id/ - // register/ - // innit/ - // complete - log!("{}", path); - log!("{:?} .len = {}", path_parts, path_parts.len()); let (status, content, body_text) = if path_parts.len() >= 2 { match path_parts[1] { "get" => match path_parts[2] { @@ -90,7 +83,7 @@ pub async fn handle( not_found() } } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_id(id).await + get_by_user_id(id).await { if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { if let Ok((public_key, ip_address)) = @@ -119,14 +112,131 @@ pub async fn handle( } // get/id/ "id" => { - let username = path_parts[3]; - bad_request() + let username = path_parts[2]; + if username.is_empty() { + not_found() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ) + .to_json() + .to_string(), + ) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } } "public_key" => ( StatusCode::OK, "application/json", public_key_to_base64(&get_public_key()), ), + "user" => { + let id = path_parts[2]; + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ) + .to_json() + .to_string(), + ) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } + } _ => { let id = path_parts[2]; let id: i64 = id.parse().unwrap_or(0); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index d9ba663..f1bd08f 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,11 +1,7 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::sql::iota_omikron_tracker::{ - track_iota_omikron, untrack_by_omikron as untrack_iota_by_omikron, untrack_iota, -}; -use crate::sql::sql::get_omikron_by_id; -use crate::sql::user_online_tracker::{ - track_user_omikron, untrack_by_omikron as untrack_user_by_omikron, untrack_user, -}; +use crate::sql::connection_status::ConnectionType; +use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; +use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; use crate::{get_private_key, log_out}; @@ -18,6 +14,7 @@ use futures::stream::SplitStream; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use json::JsonValue; +use json::number::Number; use rand::Rng; use rand::distributions::Alphanumeric; use std::sync::Arc; @@ -73,7 +70,7 @@ impl OmikronConnection { } sender.send(message_text).await.unwrap(); } - pub async fn get_user_id(&self) -> i64 { + pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await } pub async fn is_identified(&self) -> bool { @@ -102,137 +99,168 @@ impl OmikronConnection { return; } - // Handle identification - if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { - let omikron_id = cv - .get_data(DataTypes::omikron) - .unwrap_or(&JsonValue::Null) - .as_i64() - .unwrap_or(0); - match get_omikron_by_id(omikron_id).await { - Ok((public_key, _)) => { - // Generate Challenge, encrypt it and send it to the omikron - *self.omikron_id.write().await = omikron_id; + // If not yet identified + if !self.is_identified().await { + // handle identification + if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { + let omikron_id = cv + .get_data(DataTypes::omikron) + .unwrap_or(&JsonValue::Null) + .as_i64() + .unwrap_or(0); + match get_omikron_by_id(omikron_id).await { + Ok((public_key, _)) => { + // Generate Challenge, encrypt it and send it to the omikron + *self.omikron_id.write().await = omikron_id; - let challenge_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); + let challenge_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); - *self.challenge.write().await = challenge_str.clone(); + *self.challenge.write().await = challenge_str.clone(); - let user_public_key_bytes = match STANDARD.decode(&public_key) { - Ok(bytes) => bytes, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_omikron_id, - ) - .await; - return; - } - }; - *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - - let omikron_pub_key: PublicKey = - match PublicKey::from_bytes(&user_public_key_bytes) { - Some(key) => key, - None => { + let user_public_key_bytes = match STANDARD.decode(&public_key) { + Ok(bytes) => bytes, + Err(_) => { self.send_error_response( &cv.get_id(), - CommunicationType::error_invalid_public_key, + CommunicationType::error_invalid_omikron_id, ) .await; return; } }; + *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - let encrypted_challenge = - encrypt(get_private_key(), omikron_pub_key, &challenge_str) - .unwrap_or("".to_string()); + let omikron_pub_key: PublicKey = + match PublicKey::from_bytes(&user_public_key_bytes) { + Some(key) => key, + _ => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + return; + } + }; - let response = CommunicationValue::new(CommunicationType::challenge) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), + let encrypted_challenge = + encrypt(get_private_key(), omikron_pub_key, &challenge_str) + .unwrap_or("".to_string()); + + let response = CommunicationValue::new(CommunicationType::challenge) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), + ) + .add_data_str(DataTypes::challenge, encrypted_challenge) + .with_id(cv.get_id()); + + self.send_message(&response).await; + *self.identified.write().await = true; + return; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error_not_authenticated) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), ) - .add_data_str(DataTypes::challenge, encrypted_challenge) - .with_id(cv.get_id()); + .await; - self.send_message(&response).await; - *self.identified.write().await = true; - return; + return; + } } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error_not_authenticated) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + } + + // Handle challenge response + if *self.identified.read().await + && !*self.challenged.read().await + && cv.is_type(CommunicationType::challenge_response) + { + let client_response = cv + .get_data(DataTypes::challenge) + .unwrap_or(&JsonValue::Null) + .as_str() + .unwrap_or(""); + let expected_challenge = self.challenge.read().await.clone(); + + if client_response == expected_challenge { + *self.challenged.write().await = true; + + let response = + CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()); + let _ = sql::set_omikron_active(self.get_omikron_id().await, true); + self.send_message(&response).await; + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_challenge, ) .await; - - return; + self.close().await; } + return; } - } - // Handle challenge response - if *self.identified.read().await - && !*self.challenged.read().await - && cv.is_type(CommunicationType::challenge_response) - { - let client_response = cv - .get_data(DataTypes::challenge) - .unwrap_or(&JsonValue::Null) - .as_str() - .unwrap_or(""); - let expected_challenge = self.challenge.read().await.clone(); - - if client_response == expected_challenge { - *self.challenged.write().await = true; - - let response = CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()); - - self.send_message(&response).await; - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_challenge) - .await; - self.close().await; - } - return; - } - - if !self.is_identified().await { + // if not identified && not identifying self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) .await; self.close().await; return; } - let omikron_id = self.get_user_id().await; + let omikron_id = self.get_omikron_id().await; if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - track_user_omikron(user_id, omikron_id).await; + user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id) + .await; } return; } if cv.is_type(CommunicationType::user_disconnected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - untrack_user(user_id).await; + if let Some(status) = user_online_tracker::get_user_status(user_id).await { + user_online_tracker::track_user_status( + user_id, + ConnectionType::UserOffline, + status.omikron_id, + ) + .await; + } } return; } if cv.is_type(CommunicationType::iota_connected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - track_iota_omikron(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for user in users { + user_online_tracker::track_user_status( + user.0, + ConnectionType::UserOffline, + omikron_id, + ) + .await; + } + } } return; } if cv.is_type(CommunicationType::iota_disconnected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - untrack_iota(iota_id).await; + let iota_offline = + user_online_tracker::untrack_iota_connection(iota_id, omikron_id).await; + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids).await; + } + } } return; } @@ -242,7 +270,12 @@ impl OmikronConnection { { for user_id_json in user_ids { if let Some(user_id) = user_id_json.as_i64() { - track_user_omikron(user_id, omikron_id).await; + user_online_tracker::track_user_status( + user_id, + ConnectionType::Online, + omikron_id, + ) + .await; } } } @@ -251,12 +284,286 @@ impl OmikronConnection { { for iota_id_json in iota_ids { if let Some(iota_id) = iota_id_json.as_i64() { - track_iota_omikron(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id).await; } } } return; } + if cv.is_type(CommunicationType::get_user_data) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { + if let Some(user_id) = user_id.as_i64() { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = get_by_user_id(user_id).await + { + let mut response = + CommunicationValue::new(CommunicationType::get_user_data) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ); + + let user_status = user_online_tracker::get_user_status(id).await; + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .into_iter() + .map(|id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + if let Some(user_status) = user_status { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(user_status.connection_type.to_string()), + ); + response = response.add_data( + DataTypes::omikron_id, + JsonValue::Number(Number::from(user_status.omikron_id)), + ); + } else { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(ConnectionType::IotaOffline.to_string()), + ); + } + + self.send_message(&response).await; + return; + } + } + } + if let Some(username) = cv.get_data(DataTypes::username).cloned() { + if let Some(username) = username.as_str() { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = get_by_username(username).await + { + let mut response = + CommunicationValue::new(CommunicationType::get_user_data) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ); + + let user_status = user_online_tracker::get_user_status(id).await; + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + if let Some(user_status) = user_status { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(user_status.connection_type.to_string()), + ); + response = response.add_data( + DataTypes::omikron_id, + JsonValue::Number(Number::from(user_status.omikron_id)), + ); + } else { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(ConnectionType::IotaOffline.to_string()), + ); + } + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + self.send_message(&response).await; + return; + } + } + } + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send_message(&response).await; + + return; + } + if cv.is_type(CommunicationType::get_iota_data) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).cloned() { + if let Some(iota_id) = iota_id.as_i64() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + self.send_message(&response).await; + return; + } + } + } + if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { + if let Some(user_id) = user_id.as_i64() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(user_id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + self.send_message(&response).await; + return; + } + } + } + } + if let Some(username) = cv.get_data(DataTypes::username).cloned() { + if let Some(username) = username.as_str() { + if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_username(username).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(user_id)), + ) + .add_data_str(DataTypes::username, username.to_string()) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + self.send_message(&response).await; + return; + } + } + } + } + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send_message(&response).await; + + return; + } + if cv.is_type(CommunicationType::change_user_data) {} + if cv.is_type(CommunicationType::change_iota_data) {} } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { @@ -265,14 +572,16 @@ impl OmikronConnection { } pub async fn close(&self) { let mut sender = self.sender.write().await; + if self.is_identified().await { + let _ = sql::set_omikron_active(self.get_omikron_id().await, false); + } let _ = sender.close().await; } pub async fn handle_close(self: Arc) { if self.is_identified().await { - let omikron_id = self.get_user_id().await; + let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { - untrack_iota_by_omikron(omikron_id).await; - untrack_user_by_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; } } } diff --git a/src/server/server.rs b/src/server/server.rs index 263ff88..da1763d 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,12 +1,11 @@ use crate::log; use crate::server::api; -use crate::server::omikron_connection::OmikronConnection; +use crate::server::socket; use crate::util::file_util::load_file_buf; use base64::Engine; use base64::engine::general_purpose::STANDARD; use bytes::Bytes; -use futures::StreamExt; use futures_util::TryFutureExt; use http_body_util::BodyExt; use http_body_util::Full; @@ -30,9 +29,8 @@ use std::{future::Future, pin::Pin, time::Duration}; use tokio::net::TcpListener; use tokio::sync::broadcast; use tokio_rustls::TlsAcceptor; -use tokio_tungstenite::{WebSocketStream, accept_async}; use tower::Service; -use tungstenite::Message; + #[derive(Clone)] struct HttpService { peer_addr: SocketAddr, @@ -60,7 +58,7 @@ impl Service> for HttpService { let headers = parts.headers.clone(); let fut = async move { - let is_websocket_upgrade = path == "/ws/omikron" + let is_websocket_upgrade = path == "/ws" && method == Method::GET && headers .get("connection") @@ -94,39 +92,7 @@ impl Service> for HttpService { let upgrades = upgrade::on(req_for_upgrade); log!("Handling WebSocket upgrade"); - // Spawn upgrade handling to avoid blocking the service call - tokio::spawn(async move { - match upgrades.await { - Ok(upgraded_stream) => { - log!("Valid WebSocket upgrade"); - let raw_stream = TokioIo::new(upgraded_stream); - - let ws_stream = WebSocketStream::from_raw_socket( - raw_stream, - tungstenite::protocol::Role::Server, - None, - ) - .await; - log!( - "WebSocket handshake successful, handling connection for Omikron" - ); - - // Split stream for OmikronConnection - let (writer, reader) = ws_stream.split(); - - // INTEGRATION START - // Erstelle die Connection und starte den Handler - let connection = OmikronConnection::new(writer, reader); - - // Handler in separatem Task starten - start_omikron_handler(connection).await; - // INTEGRATION END - } - Err(e) => { - log!("WebSocket upgrade failed after response: {:?}", e); - } - } - }); + socket::handle(path, upgrades); log!("Handled WebSocket connection initiation"); Ok(response) @@ -192,7 +158,6 @@ async fn run_http_server(port: u16) -> bool { port ); - // Create a broadcast channel for graceful shutdown signal let (shutdown_tx, _) = broadcast::channel::<()>(1); tokio::spawn(async move { @@ -205,7 +170,6 @@ async fn run_http_server(port: u16) -> bool { let service = HttpService { peer_addr: addr }; let io = TokioIo::new(stream); - // Subscribe to the shutdown signal for this specific connection let mut rx = shutdown_tx.subscribe(); tokio::spawn(async move { @@ -216,7 +180,6 @@ async fn run_http_server(port: u16) -> bool { .serve_connection(io, TowerToHyperService::new(service)) .with_upgrades(); - // Wait for either the connection to finish naturally OR the shutdown signal tokio::select! { res = conn => { if let Err(err) = res { @@ -276,13 +239,11 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { port ); - // Create a broadcast channel for graceful shutdown signal let (shutdown_tx, _) = broadcast::channel::<()>(1); tokio::spawn(async move { loop { tokio::select! { - // Monitor for shutdown signal _ = async { loop { tokio::time::sleep(Duration::from_millis(100)).await; @@ -294,7 +255,6 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { break; } - // Accept new connections accepted = listener.accept() => { match accepted { std::result::Result::Ok((stream, addr)) => { @@ -317,14 +277,12 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { }; let io = TokioIo::new(tls_stream); - // Prepare connection future let conn = http1::Builder::new() .preserve_header_case(true) .title_case_headers(true) .serve_connection(io, TowerToHyperService::new(service)) .with_upgrades(); - // Wait for either the connection to finish naturally OR the shutdown signal tokio::select! { res = conn => { if let Err(err) = res { @@ -363,7 +321,7 @@ pub async fn start(port: u16) -> bool { match tls_result { Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, - Ok(None) => run_http_server(port).await, + Ok(_) => run_http_server(port).await, Err(e) => { log!("Fatal error during TLS config load: {}", e); false @@ -376,7 +334,7 @@ fn calculate_accept_key(key: &str) -> String { sha1.update(key.as_bytes()); sha1.update(websocket_guid.as_bytes()); let result = sha1.finalize(); - STANDARD.encode(result) // Base64 encode the result + STANDARD.encode(result) } /// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. @@ -391,7 +349,7 @@ fn load_tls_config() -> Result>, Box> { log!("TLS certificate 'certs/cert.pem' not found."); return Ok(None); } - Err(e) => return Err(e.into()), // Other IO error + Err(e) => return Err(e.into()), }; let key_file_buf = match key_file_res { @@ -400,7 +358,7 @@ fn load_tls_config() -> Result>, Box> { log!("TLS key 'certs/cert.key' not found."); return Ok(None); } - Err(e) => return Err(e.into()), // Other IO error + Err(e) => return Err(e.into()), }; // Continue with configuration if both files were found @@ -411,12 +369,12 @@ fn load_tls_config() -> Result>, Box> { // PKCS8 let mut key_reader = BufReader::new(key_file_buf); let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion + .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; if key_ders.is_empty() { // RSA - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; @@ -424,14 +382,14 @@ fn load_tls_config() -> Result>, Box> { if key_ders.is_empty() { // EC - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; } if key_ders.is_empty() { - return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); + return Err("No valid private keys found in key file (Tried PKCS8, RSA and EC).".into()); } let config = rustls::ServerConfig::builder() @@ -441,44 +399,3 @@ fn load_tls_config() -> Result>, Box> { Ok(Some(Arc::new(config))) } -// In deiner Server-Logik, wo OmikronConnection initialisiert wird: - -pub async fn start_omikron_handler(connection: Arc) { - loop { - let msg = match { - let mut receiver = connection.receiver.write().await; - receiver.next().await - } { - Some(Ok(msg)) => msg, - Some(Err(e)) => { - log!("WS Error: {}", e); - break; - } - None => break, // Stream ended - }; - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Ping(_) => { - let pong_response = crate::data::communication::CommunicationValue::new( - crate::data::communication::CommunicationType::pong, - ); - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.send_message(&pong_response).await; - }); - } - Message::Close(_) => { - break; - } - // Other message types like Binary, Pong are ignored. - _ => {} - } - } - connection.handle_close().await; -} diff --git a/src/server/socket.rs b/src/server/socket.rs index 6cc825d..6b48154 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,63 +1,70 @@ use std::sync::Arc; use futures::StreamExt; -use futures::stream::SplitSink; -use futures::stream::SplitStream; -use hyper::upgrade::Upgraded; +use hyper::upgrade::OnUpgrade; use hyper_util::rt::TokioIo; +use tokio_tungstenite::WebSocketStream; use tungstenite::Message; use crate::log; use crate::server::omikron_connection::OmikronConnection; -pub fn handle( - path: String, - writer: SplitSink>, Message>, - reader: SplitStream>>, -) { - log!("handling"); +pub fn handle(path: String, upgrades: OnUpgrade) { tokio::spawn(async move { - if path.starts_with("/ws/phi/") { - } else if path.starts_with("/ws/omikron/") { - let community_conn: Arc = - Arc::from(OmikronConnection::new(writer, reader)); - loop { - let msg_result: Option> = { - let mut session_lock = community_conn.receiver.write().await; - session_lock.next().await - }; + match upgrades.await { + Ok(upgraded_stream) => { + log!("Valid WebSocket upgrade"); + let raw_stream = TokioIo::new(upgraded_stream); - match msg_result { - Some(Ok(msg)) => { - if msg.is_text() { - let text = msg.into_text().unwrap(); - community_conn - .clone() - .handle_message(text.to_string()) - .await; - } else if msg.is_ping() { - let pong_response = crate::data::communication::CommunicationValue::new( - crate::data::communication::CommunicationType::pong, - ); - community_conn.send_message(&pong_response).await; - } else if msg.is_close() { - log!("Closing: {}", msg); - community_conn.handle_close().await; - return; - } - } - Some(Err(e)) => { - log!("Closing ERR: {}", e); - community_conn.handle_close().await; - return; - } - None => { - log!("Closed Session me!"); - community_conn.handle_close().await; - return; - } + let ws_stream = WebSocketStream::from_raw_socket( + raw_stream, + tungstenite::protocol::Role::Server, + None, + ) + .await; + log!( + "WebSocket handshake successful, handling connection for {}", + path + ); + + let (writer, reader) = ws_stream.split(); + if path == "/ws/omikron" { + let connection = OmikronConnection::new(writer, reader); + start_connecteable_handler(connection).await; } } + Err(e) => { + log!("WebSocket upgrade failed after response: {:?}", e); + } } }); } +pub async fn start_connecteable_handler(connection: Arc) { + loop { + let msg = match { + let mut receiver = connection.receiver.write().await; + receiver.next().await + } { + Some(Ok(msg)) => msg, + Some(Err(e)) => { + log!("WS Error: {}", e); + break; + } + _ => break, + }; + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + _ => {} + } + } + connection.handle_close().await; +} diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs new file mode 100644 index 0000000..5e7c68f --- /dev/null +++ b/src/sql/connection_status.rs @@ -0,0 +1,38 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ConnectionType { + Online, + UserOffline, + IotaOffline, + Away, + DoNotDisturb, +} +impl ConnectionType { + pub fn to_str(&self) -> &str { + match self { + ConnectionType::Online => "online", + ConnectionType::UserOffline => "user_offline", + ConnectionType::IotaOffline => "iota_offline", + ConnectionType::Away => "away", + ConnectionType::DoNotDisturb => "do_not_disturb", + } + } + pub fn to_string(&self) -> String { + match self { + ConnectionType::Online => "online".to_string(), + ConnectionType::UserOffline => "user_offline".to_string(), + ConnectionType::IotaOffline => "iota_offline".to_string(), + ConnectionType::Away => "away".to_string(), + ConnectionType::DoNotDisturb => "do_not_disturb".to_string(), + } + } + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "online" => Some(ConnectionType::Online), + "user_offline" => Some(ConnectionType::UserOffline), + "iota_offline" => Some(ConnectionType::IotaOffline), + "away" => Some(ConnectionType::Away), + "do_not_disturb" => Some(ConnectionType::DoNotDisturb), + _ => None, + } + } +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 583d27a..033fd01 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,3 +1,4 @@ +pub mod connection_status; pub mod iota_omikron_tracker; pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs index a5463d7..251ecb8 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -202,11 +202,11 @@ pub async fn get_by_username( String::from_utf8_lossy(&token).to_string(), )) } - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } -pub async fn get_by_id( +pub async fn get_by_user_id( id: i64, ) -> Result< ( @@ -265,10 +265,73 @@ pub async fn get_by_id( String::from_utf8_lossy(&token).to_string(), )) } - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } +pub async fn get_users_by_iota_id( + iota_id_param: i64, +) -> Result< + Vec<( + i64, + i64, + String, + String, + String, + String, + String, + i32, + i64, + String, + String, + String, + )>, + sqlx::Error, +> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + let rows = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", + ) + .bind(iota_id_param) + .fetch_all(pool) + .await?; + + let mut users = Vec::new(); + for row in rows { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let avatar: Vec = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + users.push(( + id, + iota_id, + username, + String::from_utf8_lossy(&display).to_string(), + String::from_utf8_lossy(&status).to_string(), + String::from_utf8_lossy(&about).to_string(), + String::from_utf8_lossy(&avatar).to_string(), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )); + } + + Ok(users) +} + pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -514,7 +577,7 @@ pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&ip_address).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } @@ -534,7 +597,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&ip_address).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 0cae3fa..95e94c6 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,27 +1,100 @@ +use crate::sql; +use crate::sql::connection_status::ConnectionType; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -static USER_OMIKRON_MAP: Lazy>>> = +#[derive(Debug, Clone)] +pub struct UserStatus { + pub connection_type: ConnectionType, + pub omikron_id: i64, +} + +// IotaID -> Vec +static IOTA_OMIKRON_CONNECTIONS: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); -pub async fn track_user_omikron(user: i64, omikron: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.insert(user, omikron); +// UserID -> UserStatus +static USER_STATUS_MAP: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn track_iota_connection(iota_id: i64, omikron_id: i64) { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + let connections = iota_map.entry(iota_id).or_default(); + if !connections.contains(&omikron_id) { + connections.push(omikron_id); + } } -pub async fn get_omikron_for_user(user: i64) -> Option { - let c = USER_OMIKRON_MAP.read().await; - c.get(&user).cloned() +pub async fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + if let Some(connections) = iota_map.get_mut(&iota_id) { + connections.retain(|&id| id != omikron_id); + if connections.is_empty() { + iota_map.remove(&iota_id); + return true; // Iota is now offline + } + } + false } -pub async fn untrack_user(user: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.remove(&user); +pub async fn get_iota_omikron_connections(iota_id: i64) -> Option> { + let iota_map = IOTA_OMIKRON_CONNECTIONS.read().await; + iota_map.get(&iota_id).cloned() } -pub async fn untrack_by_omikron(omikron: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.retain(|_, v| *v != omikron); +pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { + let mut user_map = USER_STATUS_MAP.write().await; + user_map.insert( + user_id, + UserStatus { + connection_type: status, + omikron_id, + }, + ); +} + +pub async fn get_user_status(user_id: i64) -> Option { + let user_map = USER_STATUS_MAP.read().await; + user_map.get(&user_id).cloned() +} + +pub async fn untrack_user(user_id: i64) { + let mut user_map = USER_STATUS_MAP.write().await; + user_map.remove(&user_id); +} + +pub async fn untrack_many_users(user_ids: &[i64]) { + let mut user_map = USER_STATUS_MAP.write().await; + for user_id in user_ids { + user_map.remove(user_id); + } +} + +pub async fn untrack_omikron(omikron_id: i64) { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + let mut user_map = USER_STATUS_MAP.write().await; + + let mut offline_iotas = Vec::new(); + + iota_map.retain(|iota_id, connections| { + connections.retain(|id| *id != omikron_id); + if connections.is_empty() { + offline_iotas.push(*iota_id); + false + } else { + true + } + }); + + user_map.retain(|_, status| status.omikron_id != omikron_id); + + for iota_id in offline_iotas { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { + for user in users { + user_map.remove(&user.0); + } + } + } } From 97f0a76c6c5950c6c425270715ece0eee666ff42 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 7 Jan 2026 15:54:05 +0100 Subject: [PATCH 012/220] Format, Status, User Stati & Endpoints --- Cargo.lock | 260 +++++++++------- Cargo.toml | 1 + src/data/communication.rs | 24 ++ src/server/api.rs | 142 ++++++++- src/server/omikron_connection.rs | 511 +++++++++++++++++++++++++------ src/server/server.rs | 107 +------ src/server/socket.rs | 101 +++--- src/sql/connection_status.rs | 38 +++ src/sql/mod.rs | 1 + src/sql/sql.rs | 73 ++++- src/sql/user_online_tracker.rs | 99 +++++- 11 files changed, 962 insertions(+), 395 deletions(-) create mode 100644 src/sql/connection_status.rs diff --git a/Cargo.lock b/Cargo.lock index 45d3a1b..05b9215 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.1.0" dependencies = [ "aes-gcm", "ansi_term", + "async-trait", "async-tungstenite", "axum", "base64 0.22.1", @@ -27,7 +28,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "reqwest", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pemfile 2.2.0", "sha1", "sha2", @@ -174,7 +175,7 @@ dependencies = [ "async-channel 2.5.0", "async-executor", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "blocking", "futures-lite 2.6.1", "once_cell", @@ -229,9 +230,9 @@ dependencies = [ [[package]] name = "async-lock" -version = "3.4.1" +version = "3.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" dependencies = [ "event-listener 5.4.1", "event-listener-strategy", @@ -259,7 +260,7 @@ dependencies = [ "async-channel 1.9.0", "async-global-executor", "async-io 2.6.0", - "async-lock 3.4.1", + "async-lock 3.4.2", "crossbeam-utils", "futures-channel", "futures-core", @@ -296,10 +297,21 @@ dependencies = [ ] [[package]] -name = "async-tungstenite" -version = "0.32.0" +name = "async-trait" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f89c129ab749940f95509d84950c62092c8b4bc6e386ddb162229037a6ec91" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-tungstenite" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4" dependencies = [ "async-native-tls", "async-std", @@ -322,7 +334,7 @@ dependencies = [ "tokio-openssl", "tokio-rustls", "tungstenite", - "webpki-roots 1.0.4", + "webpki-roots 1.0.5", ] [[package]] @@ -348,9 +360,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" dependencies = [ "aws-lc-sys", "zeroize", @@ -358,9 +370,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" dependencies = [ "cc", "cmake", @@ -370,9 +382,9 @@ dependencies = [ [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -403,9 +415,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59446ce19cd142f8833f856eb31f3eb097812d1479ab224f54d72428ca21ea22" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", @@ -449,9 +461,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" +checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" [[package]] name = "bitflags" @@ -492,9 +504,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "byteorder" @@ -519,9 +531,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.49" +version = "1.2.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" dependencies = [ "find-msvc-tools", "jobserver", @@ -531,9 +543,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.20.4" +version = "0.20.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9acd0bdbbf4b2612d09f52ba61da432140cb10930354079d0d53fafc12968726" +checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720" dependencies = [ "smallvec", "target-lexicon", @@ -557,9 +569,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.56" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b042e5d8a74ae91bb0961acd039822472ec99f8ab0948cbf6d1369588f8be586" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -920,9 +932,9 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" [[package]] name = "flate2" @@ -1167,9 +1179,9 @@ checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "gio" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daeff3dd716d1ba91850b976b76a1c2d28f99ef6c1602cd8fdaa8fab8017fd9c" +checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" dependencies = [ "futures-channel", "futures-core", @@ -1184,9 +1196,9 @@ dependencies = [ [[package]] name = "gio-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" dependencies = [ "glib-sys", "gobject-sys", @@ -1197,9 +1209,9 @@ dependencies = [ [[package]] name = "glib" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9dbecb1c33e483a98be4acfea2ab369e1c28f517c6eadb674537409c25c4b2" +checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ "bitflags 2.10.0", "futures-channel", @@ -1218,9 +1230,9 @@ dependencies = [ [[package]] name = "glib-macros" -version = "0.21.4" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880e524e0085f3546cfb38532b2c202c0d64741d9977a6e4aa24704bfc9f19fb" +checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" dependencies = [ "heck", "proc-macro-crate", @@ -1231,9 +1243,9 @@ dependencies = [ [[package]] name = "glib-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" dependencies = [ "libc", "system-deps", @@ -1259,9 +1271,9 @@ dependencies = [ [[package]] name = "gobject-sys" -version = "0.21.2" +version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" dependencies = [ "glib-sys", "libc", @@ -1270,9 +1282,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1447,7 +1459,7 @@ dependencies = [ "http", "hyper", "hyper-util", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1660,9 +1672,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -1670,9 +1682,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "jobserver" @@ -1726,9 +1738,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.178" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libm" @@ -1738,13 +1750,13 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.6.0", + "redox_syscall 0.7.0", ] [[package]] @@ -1759,9 +1771,9 @@ dependencies = [ [[package]] name = "libz-rs-sys" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15413ef615ad868d4d65dce091cb233b229419c7c0c4bcaa746c0901c49ff39c" +checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" dependencies = [ "zlib-rs", ] @@ -1870,7 +1882,7 @@ dependencies = [ "libc", "log", "openssl", - "openssl-probe", + "openssl-probe 0.1.6", "openssl-sys", "schannel", "security-framework 2.11.1", @@ -1886,9 +1898,9 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" dependencies = [ "winapi", ] @@ -2017,6 +2029,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +[[package]] +name = "openssl-probe" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" + [[package]] name = "openssl-sys" version = "0.9.111" @@ -2313,18 +2331,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" dependencies = [ "proc-macro2", ] @@ -2411,9 +2429,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" dependencies = [ "bitflags 2.10.0", ] @@ -2503,9 +2521,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", @@ -2568,9 +2586,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -2583,11 +2601,11 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe", + "openssl-probe 0.2.0", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -2613,9 +2631,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" dependencies = [ "zeroize", ] @@ -2650,9 +2668,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" [[package]] name = "same-file" @@ -2756,15 +2774,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -2780,9 +2798,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" dependencies = [ "serde_core", ] @@ -3129,9 +3147,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -3214,9 +3232,9 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" dependencies = [ "fastrand 2.3.0", "getrandom 0.3.4", @@ -3320,9 +3338,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -3373,7 +3391,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.35", + "rustls 0.23.36", "tokio", ] @@ -3391,9 +3409,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.17" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -3408,9 +3426,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.8" +version = "0.9.10+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" dependencies = [ "indexmap", "serde_core", @@ -3423,18 +3441,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", @@ -3444,18 +3462,18 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" @@ -3505,9 +3523,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3528,9 +3546,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3576,7 +3594,7 @@ dependencies = [ "log", "native-tls", "rand 0.9.2", - "rustls 0.23.35", + "rustls 0.23.36", "rustls-pki-types", "sha1", "thiserror 2.0.17", @@ -3635,9 +3653,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -3833,9 +3851,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" dependencies = [ "rustls-pki-types", ] @@ -4251,9 +4269,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -4306,18 +4324,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" dependencies = [ "proc-macro2", "quote", @@ -4356,9 +4374,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", @@ -4427,9 +4445,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f936044d677be1a1168fae1d03b583a285a5dd9d8cbf7b24c23aa1fc775235" +checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 1331a56..d5e2dcb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ edition = "2024" [dependencies] aes-gcm = "*" ansi_term = "0.12.1" +async-trait = "0.1.89" async-tungstenite = { version = "0.32.0", features = ["futures-03-sink", "futures-util", "handshake", "__rustls-tls", "async-native-tls", "async-std", "async-std-runtime", "async-tls", "gio", "gio-runtime", "glib", "openssl", "real-async-native-tls", "real-async-tls", "real-native-tls", "real-tokio-native-tls", "real-tokio-openssl", "real-tokio-rustls", "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-native-tls", "tokio-openssl", "tokio-runtime", "tokio-rustls-manual-roots", "tokio-rustls-native-certs", "tokio-rustls-webpki-roots", "url", "verbose-logging", "webpki-roots" ] } axum = "0.8.7" base64 = "0.22.1" diff --git a/src/data/communication.rs b/src/data/communication.rs index 58fd957..e21dcae 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -78,6 +78,9 @@ pub enum DataTypes { communities, rho_connections, user, + online_status, + omikron_id, + omikron_connections, } impl DataTypes { @@ -155,6 +158,9 @@ impl DataTypes { "communities" => DataTypes::communities, "rhoconnections" => DataTypes::rho_connections, "user" => DataTypes::user, + "onlinestatus" => DataTypes::online_status, + "omikronid" => DataTypes::omikron_id, + "omikronconnections" => DataTypes::omikron_connections, _ => DataTypes::error_type, // fallback if unknown } } @@ -231,6 +237,15 @@ pub enum CommunicationType { iota_connected, iota_disconnected, sync_client_iota_status, + + get_user_data, + get_iota_data, + + change_user_data, + change_iota_data, + + start_register, + complete_register, } impl CommunicationType { pub fn parse(p0: String) -> CommunicationType { @@ -305,6 +320,15 @@ impl CommunicationType { "userdisconnected" => CommunicationType::user_disconnected, "syncclientiotastatus" => CommunicationType::sync_client_iota_status, + "getuserdata" => CommunicationType::get_user_data, + "getiotadata" => CommunicationType::get_iota_data, + + "changeuserdata" => CommunicationType::change_user_data, + "changeiotadata" => CommunicationType::change_iota_data, + + "startregister" => CommunicationType::start_register, + "completeregister" => CommunicationType::complete_register, + _ => CommunicationType::error, } } diff --git a/src/server/api.rs b/src/server/api.rs index 3b5fbcc..69077a4 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,13 +1,10 @@ -use crate::{get_public_key, log}; +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::get_public_key; +use crate::sql::sql; use crate::{ sql::{ - iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, - sql::{ - change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, - change_keys, change_status, change_username, get_by_id, get_by_username, - get_iota_by_id, get_omikron_by_id, get_random_omikron, get_register_id, - register_complete_iota, register_complete_user, - }, + iota_omikron_tracker::get_omikron_for_iota, + sql::{get_by_user_id, get_omikron_by_id, get_random_omikron}, }, util::crypto_helper::public_key_to_base64, }; @@ -16,6 +13,7 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use json::JsonValue; +use json::number::Number; pub async fn handle( path: &str, @@ -37,11 +35,6 @@ pub async fn handle( // get/ // omikron/ // id/ - // register/ - // innit/ - // complete - log!("{}", path); - log!("{:?} .len = {}", path_parts, path_parts.len()); let (status, content, body_text) = if path_parts.len() >= 2 { match path_parts[1] { "get" => match path_parts[2] { @@ -90,7 +83,7 @@ pub async fn handle( not_found() } } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_id(id).await + get_by_user_id(id).await { if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { if let Ok((public_key, ip_address)) = @@ -119,14 +112,131 @@ pub async fn handle( } // get/id/ "id" => { - let username = path_parts[3]; - bad_request() + let username = path_parts[2]; + if username.is_empty() { + not_found() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ) + .to_json() + .to_string(), + ) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } } "public_key" => ( StatusCode::OK, "application/json", public_key_to_base64(&get_public_key()), ), + "user" => { + let id = path_parts[2]; + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ) + .to_json() + .to_string(), + ) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } + } _ => { let id = path_parts[2]; let id: i64 = id.parse().unwrap_or(0); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index d9ba663..f1bd08f 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,11 +1,7 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::sql::iota_omikron_tracker::{ - track_iota_omikron, untrack_by_omikron as untrack_iota_by_omikron, untrack_iota, -}; -use crate::sql::sql::get_omikron_by_id; -use crate::sql::user_online_tracker::{ - track_user_omikron, untrack_by_omikron as untrack_user_by_omikron, untrack_user, -}; +use crate::sql::connection_status::ConnectionType; +use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; +use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; use crate::{get_private_key, log_out}; @@ -18,6 +14,7 @@ use futures::stream::SplitStream; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use json::JsonValue; +use json::number::Number; use rand::Rng; use rand::distributions::Alphanumeric; use std::sync::Arc; @@ -73,7 +70,7 @@ impl OmikronConnection { } sender.send(message_text).await.unwrap(); } - pub async fn get_user_id(&self) -> i64 { + pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await } pub async fn is_identified(&self) -> bool { @@ -102,137 +99,168 @@ impl OmikronConnection { return; } - // Handle identification - if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { - let omikron_id = cv - .get_data(DataTypes::omikron) - .unwrap_or(&JsonValue::Null) - .as_i64() - .unwrap_or(0); - match get_omikron_by_id(omikron_id).await { - Ok((public_key, _)) => { - // Generate Challenge, encrypt it and send it to the omikron - *self.omikron_id.write().await = omikron_id; + // If not yet identified + if !self.is_identified().await { + // handle identification + if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { + let omikron_id = cv + .get_data(DataTypes::omikron) + .unwrap_or(&JsonValue::Null) + .as_i64() + .unwrap_or(0); + match get_omikron_by_id(omikron_id).await { + Ok((public_key, _)) => { + // Generate Challenge, encrypt it and send it to the omikron + *self.omikron_id.write().await = omikron_id; - let challenge_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); + let challenge_str: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); - *self.challenge.write().await = challenge_str.clone(); + *self.challenge.write().await = challenge_str.clone(); - let user_public_key_bytes = match STANDARD.decode(&public_key) { - Ok(bytes) => bytes, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_omikron_id, - ) - .await; - return; - } - }; - *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - - let omikron_pub_key: PublicKey = - match PublicKey::from_bytes(&user_public_key_bytes) { - Some(key) => key, - None => { + let user_public_key_bytes = match STANDARD.decode(&public_key) { + Ok(bytes) => bytes, + Err(_) => { self.send_error_response( &cv.get_id(), - CommunicationType::error_invalid_public_key, + CommunicationType::error_invalid_omikron_id, ) .await; return; } }; + *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - let encrypted_challenge = - encrypt(get_private_key(), omikron_pub_key, &challenge_str) - .unwrap_or("".to_string()); + let omikron_pub_key: PublicKey = + match PublicKey::from_bytes(&user_public_key_bytes) { + Some(key) => key, + _ => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + return; + } + }; - let response = CommunicationValue::new(CommunicationType::challenge) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), + let encrypted_challenge = + encrypt(get_private_key(), omikron_pub_key, &challenge_str) + .unwrap_or("".to_string()); + + let response = CommunicationValue::new(CommunicationType::challenge) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), + ) + .add_data_str(DataTypes::challenge, encrypted_challenge) + .with_id(cv.get_id()); + + self.send_message(&response).await; + *self.identified.write().await = true; + return; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error_not_authenticated) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), ) - .add_data_str(DataTypes::challenge, encrypted_challenge) - .with_id(cv.get_id()); + .await; - self.send_message(&response).await; - *self.identified.write().await = true; - return; + return; + } } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error_not_authenticated) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + } + + // Handle challenge response + if *self.identified.read().await + && !*self.challenged.read().await + && cv.is_type(CommunicationType::challenge_response) + { + let client_response = cv + .get_data(DataTypes::challenge) + .unwrap_or(&JsonValue::Null) + .as_str() + .unwrap_or(""); + let expected_challenge = self.challenge.read().await.clone(); + + if client_response == expected_challenge { + *self.challenged.write().await = true; + + let response = + CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()); + let _ = sql::set_omikron_active(self.get_omikron_id().await, true); + self.send_message(&response).await; + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_challenge, ) .await; - - return; + self.close().await; } + return; } - } - // Handle challenge response - if *self.identified.read().await - && !*self.challenged.read().await - && cv.is_type(CommunicationType::challenge_response) - { - let client_response = cv - .get_data(DataTypes::challenge) - .unwrap_or(&JsonValue::Null) - .as_str() - .unwrap_or(""); - let expected_challenge = self.challenge.read().await.clone(); - - if client_response == expected_challenge { - *self.challenged.write().await = true; - - let response = CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()); - - self.send_message(&response).await; - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_challenge) - .await; - self.close().await; - } - return; - } - - if !self.is_identified().await { + // if not identified && not identifying self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) .await; self.close().await; return; } - let omikron_id = self.get_user_id().await; + let omikron_id = self.get_omikron_id().await; if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - track_user_omikron(user_id, omikron_id).await; + user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id) + .await; } return; } if cv.is_type(CommunicationType::user_disconnected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - untrack_user(user_id).await; + if let Some(status) = user_online_tracker::get_user_status(user_id).await { + user_online_tracker::track_user_status( + user_id, + ConnectionType::UserOffline, + status.omikron_id, + ) + .await; + } } return; } if cv.is_type(CommunicationType::iota_connected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - track_iota_omikron(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for user in users { + user_online_tracker::track_user_status( + user.0, + ConnectionType::UserOffline, + omikron_id, + ) + .await; + } + } } return; } if cv.is_type(CommunicationType::iota_disconnected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - untrack_iota(iota_id).await; + let iota_offline = + user_online_tracker::untrack_iota_connection(iota_id, omikron_id).await; + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids).await; + } + } } return; } @@ -242,7 +270,12 @@ impl OmikronConnection { { for user_id_json in user_ids { if let Some(user_id) = user_id_json.as_i64() { - track_user_omikron(user_id, omikron_id).await; + user_online_tracker::track_user_status( + user_id, + ConnectionType::Online, + omikron_id, + ) + .await; } } } @@ -251,12 +284,286 @@ impl OmikronConnection { { for iota_id_json in iota_ids { if let Some(iota_id) = iota_id_json.as_i64() { - track_iota_omikron(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id).await; } } } return; } + if cv.is_type(CommunicationType::get_user_data) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { + if let Some(user_id) = user_id.as_i64() { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = get_by_user_id(user_id).await + { + let mut response = + CommunicationValue::new(CommunicationType::get_user_data) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ); + + let user_status = user_online_tracker::get_user_status(id).await; + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .into_iter() + .map(|id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + if let Some(user_status) = user_status { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(user_status.connection_type.to_string()), + ); + response = response.add_data( + DataTypes::omikron_id, + JsonValue::Number(Number::from(user_status.omikron_id)), + ); + } else { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(ConnectionType::IotaOffline.to_string()), + ); + } + + self.send_message(&response).await; + return; + } + } + } + if let Some(username) = cv.get_data(DataTypes::username).cloned() { + if let Some(username) = username.as_str() { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = get_by_username(username).await + { + let mut response = + CommunicationValue::new(CommunicationType::get_user_data) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ) + .add_data_str(DataTypes::display, display) + .add_data_str(DataTypes::status, status) + .add_data_str(DataTypes::about, about) + .add_data_str(DataTypes::avatar, avatar) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data( + DataTypes::sub_end, + JsonValue::Number(Number::from(sub_end)), + ); + + let user_status = user_online_tracker::get_user_status(id).await; + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + if let Some(user_status) = user_status { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(user_status.connection_type.to_string()), + ); + response = response.add_data( + DataTypes::omikron_id, + JsonValue::Number(Number::from(user_status.omikron_id)), + ); + } else { + response = response.add_data( + DataTypes::online_status, + JsonValue::String(ConnectionType::IotaOffline.to_string()), + ); + } + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + self.send_message(&response).await; + return; + } + } + } + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send_message(&response).await; + + return; + } + if cv.is_type(CommunicationType::get_iota_data) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).cloned() { + if let Some(iota_id) = iota_id.as_i64() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + self.send_message(&response).await; + return; + } + } + } + if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { + if let Some(user_id) = user_id.as_i64() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(user_id)), + ) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + + self.send_message(&response).await; + return; + } + } + } + } + if let Some(username) = cv.get_data(DataTypes::username).cloned() { + if let Some(username) = username.as_str() { + if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_username(username).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let mut response = + CommunicationValue::new(CommunicationType::get_iota_data) + .add_data_str(DataTypes::public_key, public_key) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(user_id)), + ) + .add_data_str(DataTypes::username, username.to_string()) + .add_data( + DataTypes::iota_id, + JsonValue::Number(Number::from(iota_id)), + ); + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id) + .await + .unwrap_or_default(); + + response = response.add_data( + DataTypes::omikron_connections, + JsonValue::Array( + iota_connections + .iter() + .map(|&id| JsonValue::Number(Number::from(id))) + .collect(), + ), + ); + self.send_message(&response).await; + return; + } + } + } + } + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send_message(&response).await; + + return; + } + if cv.is_type(CommunicationType::change_user_data) {} + if cv.is_type(CommunicationType::change_iota_data) {} } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { @@ -265,14 +572,16 @@ impl OmikronConnection { } pub async fn close(&self) { let mut sender = self.sender.write().await; + if self.is_identified().await { + let _ = sql::set_omikron_active(self.get_omikron_id().await, false); + } let _ = sender.close().await; } pub async fn handle_close(self: Arc) { if self.is_identified().await { - let omikron_id = self.get_user_id().await; + let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { - untrack_iota_by_omikron(omikron_id).await; - untrack_user_by_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; } } } diff --git a/src/server/server.rs b/src/server/server.rs index 263ff88..da1763d 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,12 +1,11 @@ use crate::log; use crate::server::api; -use crate::server::omikron_connection::OmikronConnection; +use crate::server::socket; use crate::util::file_util::load_file_buf; use base64::Engine; use base64::engine::general_purpose::STANDARD; use bytes::Bytes; -use futures::StreamExt; use futures_util::TryFutureExt; use http_body_util::BodyExt; use http_body_util::Full; @@ -30,9 +29,8 @@ use std::{future::Future, pin::Pin, time::Duration}; use tokio::net::TcpListener; use tokio::sync::broadcast; use tokio_rustls::TlsAcceptor; -use tokio_tungstenite::{WebSocketStream, accept_async}; use tower::Service; -use tungstenite::Message; + #[derive(Clone)] struct HttpService { peer_addr: SocketAddr, @@ -60,7 +58,7 @@ impl Service> for HttpService { let headers = parts.headers.clone(); let fut = async move { - let is_websocket_upgrade = path == "/ws/omikron" + let is_websocket_upgrade = path == "/ws" && method == Method::GET && headers .get("connection") @@ -94,39 +92,7 @@ impl Service> for HttpService { let upgrades = upgrade::on(req_for_upgrade); log!("Handling WebSocket upgrade"); - // Spawn upgrade handling to avoid blocking the service call - tokio::spawn(async move { - match upgrades.await { - Ok(upgraded_stream) => { - log!("Valid WebSocket upgrade"); - let raw_stream = TokioIo::new(upgraded_stream); - - let ws_stream = WebSocketStream::from_raw_socket( - raw_stream, - tungstenite::protocol::Role::Server, - None, - ) - .await; - log!( - "WebSocket handshake successful, handling connection for Omikron" - ); - - // Split stream for OmikronConnection - let (writer, reader) = ws_stream.split(); - - // INTEGRATION START - // Erstelle die Connection und starte den Handler - let connection = OmikronConnection::new(writer, reader); - - // Handler in separatem Task starten - start_omikron_handler(connection).await; - // INTEGRATION END - } - Err(e) => { - log!("WebSocket upgrade failed after response: {:?}", e); - } - } - }); + socket::handle(path, upgrades); log!("Handled WebSocket connection initiation"); Ok(response) @@ -192,7 +158,6 @@ async fn run_http_server(port: u16) -> bool { port ); - // Create a broadcast channel for graceful shutdown signal let (shutdown_tx, _) = broadcast::channel::<()>(1); tokio::spawn(async move { @@ -205,7 +170,6 @@ async fn run_http_server(port: u16) -> bool { let service = HttpService { peer_addr: addr }; let io = TokioIo::new(stream); - // Subscribe to the shutdown signal for this specific connection let mut rx = shutdown_tx.subscribe(); tokio::spawn(async move { @@ -216,7 +180,6 @@ async fn run_http_server(port: u16) -> bool { .serve_connection(io, TowerToHyperService::new(service)) .with_upgrades(); - // Wait for either the connection to finish naturally OR the shutdown signal tokio::select! { res = conn => { if let Err(err) = res { @@ -276,13 +239,11 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { port ); - // Create a broadcast channel for graceful shutdown signal let (shutdown_tx, _) = broadcast::channel::<()>(1); tokio::spawn(async move { loop { tokio::select! { - // Monitor for shutdown signal _ = async { loop { tokio::time::sleep(Duration::from_millis(100)).await; @@ -294,7 +255,6 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { break; } - // Accept new connections accepted = listener.accept() => { match accepted { std::result::Result::Ok((stream, addr)) => { @@ -317,14 +277,12 @@ async fn run_tls_server(port: u16, tls_config: Arc) -> bool { }; let io = TokioIo::new(tls_stream); - // Prepare connection future let conn = http1::Builder::new() .preserve_header_case(true) .title_case_headers(true) .serve_connection(io, TowerToHyperService::new(service)) .with_upgrades(); - // Wait for either the connection to finish naturally OR the shutdown signal tokio::select! { res = conn => { if let Err(err) = res { @@ -363,7 +321,7 @@ pub async fn start(port: u16) -> bool { match tls_result { Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, - Ok(None) => run_http_server(port).await, + Ok(_) => run_http_server(port).await, Err(e) => { log!("Fatal error during TLS config load: {}", e); false @@ -376,7 +334,7 @@ fn calculate_accept_key(key: &str) -> String { sha1.update(key.as_bytes()); sha1.update(websocket_guid.as_bytes()); let result = sha1.finalize(); - STANDARD.encode(result) // Base64 encode the result + STANDARD.encode(result) } /// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. @@ -391,7 +349,7 @@ fn load_tls_config() -> Result>, Box> { log!("TLS certificate 'certs/cert.pem' not found."); return Ok(None); } - Err(e) => return Err(e.into()), // Other IO error + Err(e) => return Err(e.into()), }; let key_file_buf = match key_file_res { @@ -400,7 +358,7 @@ fn load_tls_config() -> Result>, Box> { log!("TLS key 'certs/cert.key' not found."); return Ok(None); } - Err(e) => return Err(e.into()), // Other IO error + Err(e) => return Err(e.into()), }; // Continue with configuration if both files were found @@ -411,12 +369,12 @@ fn load_tls_config() -> Result>, Box> { // PKCS8 let mut key_reader = BufReader::new(key_file_buf); let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion + .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; if key_ders.is_empty() { // RSA - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; @@ -424,14 +382,14 @@ fn load_tls_config() -> Result>, Box> { if key_ders.is_empty() { // EC - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file + key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) .map(|r| r.map(Into::into)) .collect::, io::Error>>()?; } if key_ders.is_empty() { - return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); + return Err("No valid private keys found in key file (Tried PKCS8, RSA and EC).".into()); } let config = rustls::ServerConfig::builder() @@ -441,44 +399,3 @@ fn load_tls_config() -> Result>, Box> { Ok(Some(Arc::new(config))) } -// In deiner Server-Logik, wo OmikronConnection initialisiert wird: - -pub async fn start_omikron_handler(connection: Arc) { - loop { - let msg = match { - let mut receiver = connection.receiver.write().await; - receiver.next().await - } { - Some(Ok(msg)) => msg, - Some(Err(e)) => { - log!("WS Error: {}", e); - break; - } - None => break, // Stream ended - }; - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Ping(_) => { - let pong_response = crate::data::communication::CommunicationValue::new( - crate::data::communication::CommunicationType::pong, - ); - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.send_message(&pong_response).await; - }); - } - Message::Close(_) => { - break; - } - // Other message types like Binary, Pong are ignored. - _ => {} - } - } - connection.handle_close().await; -} diff --git a/src/server/socket.rs b/src/server/socket.rs index 6cc825d..6b48154 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,63 +1,70 @@ use std::sync::Arc; use futures::StreamExt; -use futures::stream::SplitSink; -use futures::stream::SplitStream; -use hyper::upgrade::Upgraded; +use hyper::upgrade::OnUpgrade; use hyper_util::rt::TokioIo; +use tokio_tungstenite::WebSocketStream; use tungstenite::Message; use crate::log; use crate::server::omikron_connection::OmikronConnection; -pub fn handle( - path: String, - writer: SplitSink>, Message>, - reader: SplitStream>>, -) { - log!("handling"); +pub fn handle(path: String, upgrades: OnUpgrade) { tokio::spawn(async move { - if path.starts_with("/ws/phi/") { - } else if path.starts_with("/ws/omikron/") { - let community_conn: Arc = - Arc::from(OmikronConnection::new(writer, reader)); - loop { - let msg_result: Option> = { - let mut session_lock = community_conn.receiver.write().await; - session_lock.next().await - }; + match upgrades.await { + Ok(upgraded_stream) => { + log!("Valid WebSocket upgrade"); + let raw_stream = TokioIo::new(upgraded_stream); - match msg_result { - Some(Ok(msg)) => { - if msg.is_text() { - let text = msg.into_text().unwrap(); - community_conn - .clone() - .handle_message(text.to_string()) - .await; - } else if msg.is_ping() { - let pong_response = crate::data::communication::CommunicationValue::new( - crate::data::communication::CommunicationType::pong, - ); - community_conn.send_message(&pong_response).await; - } else if msg.is_close() { - log!("Closing: {}", msg); - community_conn.handle_close().await; - return; - } - } - Some(Err(e)) => { - log!("Closing ERR: {}", e); - community_conn.handle_close().await; - return; - } - None => { - log!("Closed Session me!"); - community_conn.handle_close().await; - return; - } + let ws_stream = WebSocketStream::from_raw_socket( + raw_stream, + tungstenite::protocol::Role::Server, + None, + ) + .await; + log!( + "WebSocket handshake successful, handling connection for {}", + path + ); + + let (writer, reader) = ws_stream.split(); + if path == "/ws/omikron" { + let connection = OmikronConnection::new(writer, reader); + start_connecteable_handler(connection).await; } } + Err(e) => { + log!("WebSocket upgrade failed after response: {:?}", e); + } } }); } +pub async fn start_connecteable_handler(connection: Arc) { + loop { + let msg = match { + let mut receiver = connection.receiver.write().await; + receiver.next().await + } { + Some(Ok(msg)) => msg, + Some(Err(e)) => { + log!("WS Error: {}", e); + break; + } + _ => break, + }; + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + _ => {} + } + } + connection.handle_close().await; +} diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs new file mode 100644 index 0000000..5e7c68f --- /dev/null +++ b/src/sql/connection_status.rs @@ -0,0 +1,38 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ConnectionType { + Online, + UserOffline, + IotaOffline, + Away, + DoNotDisturb, +} +impl ConnectionType { + pub fn to_str(&self) -> &str { + match self { + ConnectionType::Online => "online", + ConnectionType::UserOffline => "user_offline", + ConnectionType::IotaOffline => "iota_offline", + ConnectionType::Away => "away", + ConnectionType::DoNotDisturb => "do_not_disturb", + } + } + pub fn to_string(&self) -> String { + match self { + ConnectionType::Online => "online".to_string(), + ConnectionType::UserOffline => "user_offline".to_string(), + ConnectionType::IotaOffline => "iota_offline".to_string(), + ConnectionType::Away => "away".to_string(), + ConnectionType::DoNotDisturb => "do_not_disturb".to_string(), + } + } + pub fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "online" => Some(ConnectionType::Online), + "user_offline" => Some(ConnectionType::UserOffline), + "iota_offline" => Some(ConnectionType::IotaOffline), + "away" => Some(ConnectionType::Away), + "do_not_disturb" => Some(ConnectionType::DoNotDisturb), + _ => None, + } + } +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 583d27a..033fd01 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,3 +1,4 @@ +pub mod connection_status; pub mod iota_omikron_tracker; pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs index a5463d7..251ecb8 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -202,11 +202,11 @@ pub async fn get_by_username( String::from_utf8_lossy(&token).to_string(), )) } - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } -pub async fn get_by_id( +pub async fn get_by_user_id( id: i64, ) -> Result< ( @@ -265,10 +265,73 @@ pub async fn get_by_id( String::from_utf8_lossy(&token).to_string(), )) } - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } +pub async fn get_users_by_iota_id( + iota_id_param: i64, +) -> Result< + Vec<( + i64, + i64, + String, + String, + String, + String, + String, + i32, + i64, + String, + String, + String, + )>, + sqlx::Error, +> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + let rows = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", + ) + .bind(iota_id_param) + .fetch_all(pool) + .await?; + + let mut users = Vec::new(); + for row in rows { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Vec = row.get("display"); + let status: Vec = row.get("status"); + let about: Vec = row.get("about"); + let avatar: Vec = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + users.push(( + id, + iota_id, + username, + String::from_utf8_lossy(&display).to_string(), + String::from_utf8_lossy(&status).to_string(), + String::from_utf8_lossy(&about).to_string(), + String::from_utf8_lossy(&avatar).to_string(), + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )); + } + + Ok(users) +} + pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -514,7 +577,7 @@ pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&ip_address).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } @@ -534,7 +597,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> String::from_utf8_lossy(&public_key).to_string(), String::from_utf8_lossy(&ip_address).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), } } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 0cae3fa..95e94c6 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,27 +1,100 @@ +use crate::sql; +use crate::sql::connection_status::ConnectionType; use once_cell::sync::Lazy; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; -static USER_OMIKRON_MAP: Lazy>>> = +#[derive(Debug, Clone)] +pub struct UserStatus { + pub connection_type: ConnectionType, + pub omikron_id: i64, +} + +// IotaID -> Vec +static IOTA_OMIKRON_CONNECTIONS: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); -pub async fn track_user_omikron(user: i64, omikron: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.insert(user, omikron); +// UserID -> UserStatus +static USER_STATUS_MAP: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn track_iota_connection(iota_id: i64, omikron_id: i64) { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + let connections = iota_map.entry(iota_id).or_default(); + if !connections.contains(&omikron_id) { + connections.push(omikron_id); + } } -pub async fn get_omikron_for_user(user: i64) -> Option { - let c = USER_OMIKRON_MAP.read().await; - c.get(&user).cloned() +pub async fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + if let Some(connections) = iota_map.get_mut(&iota_id) { + connections.retain(|&id| id != omikron_id); + if connections.is_empty() { + iota_map.remove(&iota_id); + return true; // Iota is now offline + } + } + false } -pub async fn untrack_user(user: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.remove(&user); +pub async fn get_iota_omikron_connections(iota_id: i64) -> Option> { + let iota_map = IOTA_OMIKRON_CONNECTIONS.read().await; + iota_map.get(&iota_id).cloned() } -pub async fn untrack_by_omikron(omikron: i64) { - let mut c = USER_OMIKRON_MAP.write().await; - c.retain(|_, v| *v != omikron); +pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { + let mut user_map = USER_STATUS_MAP.write().await; + user_map.insert( + user_id, + UserStatus { + connection_type: status, + omikron_id, + }, + ); +} + +pub async fn get_user_status(user_id: i64) -> Option { + let user_map = USER_STATUS_MAP.read().await; + user_map.get(&user_id).cloned() +} + +pub async fn untrack_user(user_id: i64) { + let mut user_map = USER_STATUS_MAP.write().await; + user_map.remove(&user_id); +} + +pub async fn untrack_many_users(user_ids: &[i64]) { + let mut user_map = USER_STATUS_MAP.write().await; + for user_id in user_ids { + user_map.remove(user_id); + } +} + +pub async fn untrack_omikron(omikron_id: i64) { + let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; + let mut user_map = USER_STATUS_MAP.write().await; + + let mut offline_iotas = Vec::new(); + + iota_map.retain(|iota_id, connections| { + connections.retain(|id| *id != omikron_id); + if connections.is_empty() { + offline_iotas.push(*iota_id); + false + } else { + true + } + }); + + user_map.retain(|_, status| status.omikron_id != omikron_id); + + for iota_id in offline_iotas { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { + for user in users { + user_map.remove(&user.0); + } + } + } } From 06be69c5e07aadbc494c707cf546eeb1b2e8717e Mon Sep 17 00:00:00 2001 From: Alex Emmet Date: Sat, 10 Jan 2026 16:55:31 +0000 Subject: [PATCH 013/220] implemented user editing endpoints --- src/data/communication.rs | 18 +++ src/server/omikron_connection.rs | 266 ++++++++++++++++++++++++++++++- src/server/server.rs | 2 +- src/sql/sql.rs | 20 ++- 4 files changed, 296 insertions(+), 10 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index e21dcae..4dd6d43 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -81,6 +81,8 @@ pub enum DataTypes { online_status, omikron_id, omikron_connections, + reset_token, + new_token, } impl DataTypes { @@ -161,6 +163,8 @@ impl DataTypes { "onlinestatus" => DataTypes::online_status, "omikronid" => DataTypes::omikron_id, "omikronconnections" => DataTypes::omikron_connections, + "resettoken" => DataTypes::reset_token, + "newtoken" => DataTypes::new_token, _ => DataTypes::error_type, // fallback if unknown } } @@ -170,6 +174,7 @@ impl DataTypes { #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, + error_invalid_data, error_invalid_user_id, error_invalid_omikron_id, error_not_found, @@ -244,6 +249,12 @@ pub enum CommunicationType { change_user_data, change_iota_data, + get_register, + complete_register_user, + complete_register_iota, + delete_user, + delete_iota, + start_register, complete_register, } @@ -262,6 +273,7 @@ impl CommunicationType { "function" => CommunicationType::function, "update" => CommunicationType::update, "createuser" => CommunicationType::create_user, + "errorinvaliddata" => CommunicationType::error_invalid_data, "errorinvaliduserid" => CommunicationType::error_invalid_user_id, "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, "errornotfound" => CommunicationType::error_not_found, @@ -326,6 +338,12 @@ impl CommunicationType { "changeuserdata" => CommunicationType::change_user_data, "changeiotadata" => CommunicationType::change_iota_data, + "getregister" => CommunicationType::get_register, + "completeregisteruser" => CommunicationType::complete_register_user, + "completeregisteriota" => CommunicationType::complete_register_iota, + "deleteuser" => CommunicationType::delete_user, + "deleteiota" => CommunicationType::delete_iota, + "startregister" => CommunicationType::start_register, "completeregister" => CommunicationType::complete_register, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f1bd08f..bebab4a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -562,8 +562,270 @@ impl OmikronConnection { return; } - if cv.is_type(CommunicationType::change_user_data) {} - if cv.is_type(CommunicationType::change_iota_data) {} + + if cv.is_type(CommunicationType::get_register) { + let register_id = sql::get_register_id().await; + let response = CommunicationValue::new(CommunicationType::get_register) + .with_id(cv.get_id()) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(register_id)), + ); + self.send_message(&response).await; + return; + } + if cv.is_type(CommunicationType::complete_register_iota) { + if let (Some(iota_id), Some(public_key)) = ( + cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), + ) { + match sql::register_complete_iota(iota_id, public_key.to_string()).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::complete_register_user) { + if let ( + Some(user_id), + Some(username), + Some(public_key), + Some(iota_id), + Some(reset_token), + ) = ( + cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::username).and_then(|v| v.as_str()), + cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), + cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), // Assuming reset_token is sent as token + ) { + // The documentation does not specify a private_key_hash, using an empty string. + match sql::register_complete_user( + user_id, + username.to_string(), + public_key.to_string(), + "".to_string(), // private_key_hash + iota_id, + reset_token.to_string(), + ) + .await + { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::change_user_data) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + let mut success = true; + let mut error_message = String::new(); + + if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(public_key) = + cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) + { + if let Some(private_key_hash) = cv + .get_data(DataTypes::private_key_hash) + .and_then(|v| v.as_str()) + { + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), + ) + .await + { + success = false; + error_message = e.to_string(); + } + } else { + success = false; + error_message = + "private_key_hash is required when changing public_key".to_string(); + } + } + + if success { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } else { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, error_message), + ) + .await; + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::change_iota_data) { + if let (Some(user_id), Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), + cv.get_data(DataTypes::new_token).and_then(|v| v.as_str()), + ) { + match sql::get_by_user_id(user_id).await { + Ok(user) => { + let current_token = user.11; // token is the 12th element (index 11) + if current_token == reset_token { + let mut success = true; + let mut error_message = String::new(); + if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + success = false; + error_message = e.to_string(); + } + if success { + if let Err(e) = + sql::change_token(user_id, new_token.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } else { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, error_message), + ) + .await; + } + } else { + self.send_error_response( + &cv.get_id(), + // Using this for invalid token + CommunicationType::error_invalid_challenge, + ) + .await; + } + } + Err(_) => { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::delete_user) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + match sql::delete_user(user_id).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::delete_iota) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { + match sql::delete_iota(iota_id).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { diff --git a/src/server/server.rs b/src/server/server.rs index da1763d..6c5102b 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -58,7 +58,7 @@ impl Service> for HttpService { let headers = parts.headers.clone(); let fut = async move { - let is_websocket_upgrade = path == "/ws" + let is_websocket_upgrade = path.starts_with("/ws") && method == Method::GET && headers .get("connection") diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 251ecb8..bd3d283 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -427,6 +427,18 @@ pub async fn change_keys( Ok(()) } +pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET token = ? WHERE id = ?") + .bind(new_token) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} pub async fn register_complete_user( id: i64, username: String, @@ -470,9 +482,6 @@ pub async fn print_users() -> Result<(), Box> { let about: Vec = row.get("about"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); log!( "User: {:?}", @@ -484,10 +493,7 @@ pub async fn print_users() -> Result<(), Box> { String::from_utf8_lossy(&status), String::from_utf8_lossy(&about), sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token) + sub_end ) ); } From 344282c30b550ea9ba7ace6193b5384c70f787c3 Mon Sep 17 00:00:00 2001 From: Alex Emmet Date: Sat, 10 Jan 2026 16:55:31 +0000 Subject: [PATCH 014/220] implemented user editing endpoints --- src/data/communication.rs | 18 +++ src/server/omikron_connection.rs | 266 ++++++++++++++++++++++++++++++- src/server/server.rs | 2 +- src/sql/sql.rs | 20 ++- 4 files changed, 296 insertions(+), 10 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index e21dcae..4dd6d43 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -81,6 +81,8 @@ pub enum DataTypes { online_status, omikron_id, omikron_connections, + reset_token, + new_token, } impl DataTypes { @@ -161,6 +163,8 @@ impl DataTypes { "onlinestatus" => DataTypes::online_status, "omikronid" => DataTypes::omikron_id, "omikronconnections" => DataTypes::omikron_connections, + "resettoken" => DataTypes::reset_token, + "newtoken" => DataTypes::new_token, _ => DataTypes::error_type, // fallback if unknown } } @@ -170,6 +174,7 @@ impl DataTypes { #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, + error_invalid_data, error_invalid_user_id, error_invalid_omikron_id, error_not_found, @@ -244,6 +249,12 @@ pub enum CommunicationType { change_user_data, change_iota_data, + get_register, + complete_register_user, + complete_register_iota, + delete_user, + delete_iota, + start_register, complete_register, } @@ -262,6 +273,7 @@ impl CommunicationType { "function" => CommunicationType::function, "update" => CommunicationType::update, "createuser" => CommunicationType::create_user, + "errorinvaliddata" => CommunicationType::error_invalid_data, "errorinvaliduserid" => CommunicationType::error_invalid_user_id, "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, "errornotfound" => CommunicationType::error_not_found, @@ -326,6 +338,12 @@ impl CommunicationType { "changeuserdata" => CommunicationType::change_user_data, "changeiotadata" => CommunicationType::change_iota_data, + "getregister" => CommunicationType::get_register, + "completeregisteruser" => CommunicationType::complete_register_user, + "completeregisteriota" => CommunicationType::complete_register_iota, + "deleteuser" => CommunicationType::delete_user, + "deleteiota" => CommunicationType::delete_iota, + "startregister" => CommunicationType::start_register, "completeregister" => CommunicationType::complete_register, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f1bd08f..bebab4a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -562,8 +562,270 @@ impl OmikronConnection { return; } - if cv.is_type(CommunicationType::change_user_data) {} - if cv.is_type(CommunicationType::change_iota_data) {} + + if cv.is_type(CommunicationType::get_register) { + let register_id = sql::get_register_id().await; + let response = CommunicationValue::new(CommunicationType::get_register) + .with_id(cv.get_id()) + .add_data( + DataTypes::user_id, + JsonValue::Number(Number::from(register_id)), + ); + self.send_message(&response).await; + return; + } + if cv.is_type(CommunicationType::complete_register_iota) { + if let (Some(iota_id), Some(public_key)) = ( + cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), + ) { + match sql::register_complete_iota(iota_id, public_key.to_string()).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::complete_register_user) { + if let ( + Some(user_id), + Some(username), + Some(public_key), + Some(iota_id), + Some(reset_token), + ) = ( + cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::username).and_then(|v| v.as_str()), + cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), + cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), // Assuming reset_token is sent as token + ) { + // The documentation does not specify a private_key_hash, using an empty string. + match sql::register_complete_user( + user_id, + username.to_string(), + public_key.to_string(), + "".to_string(), // private_key_hash + iota_id, + reset_token.to_string(), + ) + .await + { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::change_user_data) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + let mut success = true; + let mut error_message = String::new(); + + if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(public_key) = + cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) + { + if let Some(private_key_hash) = cv + .get_data(DataTypes::private_key_hash) + .and_then(|v| v.as_str()) + { + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), + ) + .await + { + success = false; + error_message = e.to_string(); + } + } else { + success = false; + error_message = + "private_key_hash is required when changing public_key".to_string(); + } + } + + if success { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } else { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, error_message), + ) + .await; + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::change_iota_data) { + if let (Some(user_id), Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), + cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), + cv.get_data(DataTypes::new_token).and_then(|v| v.as_str()), + ) { + match sql::get_by_user_id(user_id).await { + Ok(user) => { + let current_token = user.11; // token is the 12th element (index 11) + if current_token == reset_token { + let mut success = true; + let mut error_message = String::new(); + if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + success = false; + error_message = e.to_string(); + } + if success { + if let Err(e) = + sql::change_token(user_id, new_token.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } else { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, error_message), + ) + .await; + } + } else { + self.send_error_response( + &cv.get_id(), + // Using this for invalid token + CommunicationType::error_invalid_challenge, + ) + .await; + } + } + Err(_) => { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::delete_user) { + if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { + match sql::delete_user(user_id).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } + if cv.is_type(CommunicationType::delete_iota) { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { + match sql::delete_iota(iota_id).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } + } + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + } + return; + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { diff --git a/src/server/server.rs b/src/server/server.rs index da1763d..6c5102b 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -58,7 +58,7 @@ impl Service> for HttpService { let headers = parts.headers.clone(); let fut = async move { - let is_websocket_upgrade = path == "/ws" + let is_websocket_upgrade = path.starts_with("/ws") && method == Method::GET && headers .get("connection") diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 251ecb8..bd3d283 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -427,6 +427,18 @@ pub async fn change_keys( Ok(()) } +pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("UPDATE users SET token = ? WHERE id = ?") + .bind(new_token) + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} pub async fn register_complete_user( id: i64, username: String, @@ -470,9 +482,6 @@ pub async fn print_users() -> Result<(), Box> { let about: Vec = row.get("about"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); log!( "User: {:?}", @@ -484,10 +493,7 @@ pub async fn print_users() -> Result<(), Box> { String::from_utf8_lossy(&status), String::from_utf8_lossy(&about), sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token) + sub_end ) ); } From d94cbea285d5870da11d8850c2baf4fa6b1733be Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 10 Jan 2026 23:40:36 +0100 Subject: [PATCH 015/220] omikron connection handling --- src/server/omikron_connection.rs | 157 +++++++++++++++---------------- 1 file changed, 77 insertions(+), 80 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index bebab4a..98b6f00 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -4,8 +4,7 @@ use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, log_out}; -use crate::{get_public_key, log_in}; +use crate::{get_private_key, get_public_key, log_in, log_out}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use futures::SinkExt; @@ -99,70 +98,18 @@ impl OmikronConnection { return; } - // If not yet identified if !self.is_identified().await { - // handle identification - if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + if !identified && cv.is_type(CommunicationType::identification) { let omikron_id = cv .get_data(DataTypes::omikron) - .unwrap_or(&JsonValue::Null) - .as_i64() + .and_then(|v| v.as_i64()) .unwrap_or(0); - match get_omikron_by_id(omikron_id).await { - Ok((public_key, _)) => { - // Generate Challenge, encrypt it and send it to the omikron - *self.omikron_id.write().await = omikron_id; - let challenge_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge_str.clone(); - - let user_public_key_bytes = match STANDARD.decode(&public_key) { - Ok(bytes) => bytes, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_omikron_id, - ) - .await; - return; - } - }; - *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - - let omikron_pub_key: PublicKey = - match PublicKey::from_bytes(&user_public_key_bytes) { - Some(key) => key, - _ => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_public_key, - ) - .await; - return; - } - }; - - let encrypted_challenge = - encrypt(get_private_key(), omikron_pub_key, &challenge_str) - .unwrap_or("".to_string()); - - let response = CommunicationValue::new(CommunicationType::challenge) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), - ) - .add_data_str(DataTypes::challenge, encrypted_challenge) - .with_id(cv.get_id()); - - self.send_message(&response).await; - *self.identified.write().await = true; - return; - } + let (public_key, _) = match get_omikron_by_id(omikron_id).await { + Ok(v) => v, Err(e) => { self.send_message( &CommunicationValue::new(CommunicationType::error_not_authenticated) @@ -170,32 +117,78 @@ impl OmikronConnection { .add_data_str(DataTypes::error_type, e.to_string()), ) .await; - return; } - } + }; + + let pub_key_bytes = match STANDARD.decode(&public_key) { + Ok(b) => b, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_omikron_id, + ) + .await; + return; + } + }; + + let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { + Some(k) => k, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + return; + } + }; + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.omikron_id.write().await = omikron_id; + *self.challenge.write().await = challenge.clone(); + *self.pub_key.write().await = Some(pub_key_bytes); + *self.identified.write().await = true; + + let encrypted = + encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), + ) + .add_data_str(DataTypes::challenge, encrypted); + + self.send_message(&response).await; + return; } - // Handle challenge response - if *self.identified.read().await - && !*self.challenged.read().await - && cv.is_type(CommunicationType::challenge_response) - { + // ────────────────────────────── + // Challenge response + // ────────────────────────────── + if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { let client_response = cv .get_data(DataTypes::challenge) - .unwrap_or(&JsonValue::Null) - .as_str() + .and_then(|v| v.as_str()) .unwrap_or(""); - let expected_challenge = self.challenge.read().await.clone(); - if client_response == expected_challenge { + if client_response == *self.challenge.read().await { *self.challenged.write().await = true; - - let response = - CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()); let _ = sql::set_omikron_active(self.get_omikron_id().await, true); - self.send_message(&response).await; + + self.send_message( + &CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()), + ) + .await; } else { self.send_error_response( &cv.get_id(), @@ -207,14 +200,13 @@ impl OmikronConnection { return; } - // if not identified && not identifying self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) .await; self.close().await; - return; } - let omikron_id = self.get_omikron_id().await; + + // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id) @@ -290,6 +282,8 @@ impl OmikronConnection { } return; } + + // DATA RETURN if cv.is_type(CommunicationType::get_user_data) { if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { if let Some(user_id) = user_id.as_i64() { @@ -563,6 +557,7 @@ impl OmikronConnection { return; } + // REGISTERING if cv.is_type(CommunicationType::get_register) { let register_id = sql::get_register_id().await; let response = CommunicationValue::new(CommunicationType::get_register) @@ -645,6 +640,8 @@ impl OmikronConnection { } return; } + + // CHANGING DATA if cv.is_type(CommunicationType::change_user_data) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { let mut success = true; From 84841601fb62e2e15f80bda92914d514ba9487bf Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 10 Jan 2026 23:40:36 +0100 Subject: [PATCH 016/220] omikron connection handling --- src/server/omikron_connection.rs | 157 +++++++++++++++---------------- 1 file changed, 77 insertions(+), 80 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index bebab4a..98b6f00 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -4,8 +4,7 @@ use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, log_out}; -use crate::{get_public_key, log_in}; +use crate::{get_private_key, get_public_key, log_in, log_out}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use futures::SinkExt; @@ -99,70 +98,18 @@ impl OmikronConnection { return; } - // If not yet identified if !self.is_identified().await { - // handle identification - if !*self.identified.read().await && cv.is_type(CommunicationType::identification) { + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + if !identified && cv.is_type(CommunicationType::identification) { let omikron_id = cv .get_data(DataTypes::omikron) - .unwrap_or(&JsonValue::Null) - .as_i64() + .and_then(|v| v.as_i64()) .unwrap_or(0); - match get_omikron_by_id(omikron_id).await { - Ok((public_key, _)) => { - // Generate Challenge, encrypt it and send it to the omikron - *self.omikron_id.write().await = omikron_id; - let challenge_str: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge_str.clone(); - - let user_public_key_bytes = match STANDARD.decode(&public_key) { - Ok(bytes) => bytes, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_omikron_id, - ) - .await; - return; - } - }; - *self.pub_key.write().await = Some(user_public_key_bytes.clone()); - - let omikron_pub_key: PublicKey = - match PublicKey::from_bytes(&user_public_key_bytes) { - Some(key) => key, - _ => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_public_key, - ) - .await; - return; - } - }; - - let encrypted_challenge = - encrypt(get_private_key(), omikron_pub_key, &challenge_str) - .unwrap_or("".to_string()); - - let response = CommunicationValue::new(CommunicationType::challenge) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), - ) - .add_data_str(DataTypes::challenge, encrypted_challenge) - .with_id(cv.get_id()); - - self.send_message(&response).await; - *self.identified.write().await = true; - return; - } + let (public_key, _) = match get_omikron_by_id(omikron_id).await { + Ok(v) => v, Err(e) => { self.send_message( &CommunicationValue::new(CommunicationType::error_not_authenticated) @@ -170,32 +117,78 @@ impl OmikronConnection { .add_data_str(DataTypes::error_type, e.to_string()), ) .await; - return; } - } + }; + + let pub_key_bytes = match STANDARD.decode(&public_key) { + Ok(b) => b, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_omikron_id, + ) + .await; + return; + } + }; + + let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { + Some(k) => k, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + return; + } + }; + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.omikron_id.write().await = omikron_id; + *self.challenge.write().await = challenge.clone(); + *self.pub_key.write().await = Some(pub_key_bytes); + *self.identified.write().await = true; + + let encrypted = + encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data_str( + DataTypes::public_key, + STANDARD.encode(get_public_key().as_bytes()), + ) + .add_data_str(DataTypes::challenge, encrypted); + + self.send_message(&response).await; + return; } - // Handle challenge response - if *self.identified.read().await - && !*self.challenged.read().await - && cv.is_type(CommunicationType::challenge_response) - { + // ────────────────────────────── + // Challenge response + // ────────────────────────────── + if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { let client_response = cv .get_data(DataTypes::challenge) - .unwrap_or(&JsonValue::Null) - .as_str() + .and_then(|v| v.as_str()) .unwrap_or(""); - let expected_challenge = self.challenge.read().await.clone(); - if client_response == expected_challenge { + if client_response == *self.challenge.read().await { *self.challenged.write().await = true; - - let response = - CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()); let _ = sql::set_omikron_active(self.get_omikron_id().await, true); - self.send_message(&response).await; + + self.send_message( + &CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()), + ) + .await; } else { self.send_error_response( &cv.get_id(), @@ -207,14 +200,13 @@ impl OmikronConnection { return; } - // if not identified && not identifying self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) .await; self.close().await; - return; } - let omikron_id = self.get_omikron_id().await; + + // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id) @@ -290,6 +282,8 @@ impl OmikronConnection { } return; } + + // DATA RETURN if cv.is_type(CommunicationType::get_user_data) { if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { if let Some(user_id) = user_id.as_i64() { @@ -563,6 +557,7 @@ impl OmikronConnection { return; } + // REGISTERING if cv.is_type(CommunicationType::get_register) { let register_id = sql::get_register_id().await; let response = CommunicationValue::new(CommunicationType::get_register) @@ -645,6 +640,8 @@ impl OmikronConnection { } return; } + + // CHANGING DATA if cv.is_type(CommunicationType::change_user_data) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { let mut success = true; From 81fc25eb1e770177741fd4175824dc85e26694e2 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:57:48 +0100 Subject: [PATCH 017/220] register process optional value handlin in the sql database --- src/data/communication.rs | 8 ++ src/server/api.rs | 158 ++++++++++++++++--------------- src/server/omikron_connection.rs | 92 +++++++++++++----- src/sql/sql.rs | 90 ++++++++++-------- 4 files changed, 205 insertions(+), 143 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 4dd6d43..de68e6b 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -10,6 +10,7 @@ pub enum DataTypes { error_type, accepted_ids, uuid, + register_id, settings, settings_name, chat_partner_id, @@ -92,6 +93,7 @@ impl DataTypes { match normalized.as_str() { "errortype" => DataTypes::error_type, "chatpartnerid" => DataTypes::chat_partner_id, + "registerid" => DataTypes::register_id, "uuid" => DataTypes::uuid, "settings" => DataTypes::settings, "settingsname" => DataTypes::settings_name, @@ -174,6 +176,7 @@ impl DataTypes { #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, + error_internal, error_invalid_data, error_invalid_user_id, error_invalid_omikron_id, @@ -211,6 +214,8 @@ pub enum CommunicationType { register_response, identification, identification_response, + register_iota, + register_iota_success, ping, pong, add_chat, @@ -273,6 +278,7 @@ impl CommunicationType { "function" => CommunicationType::function, "update" => CommunicationType::update, "createuser" => CommunicationType::create_user, + "errorinternal" => CommunicationType::error_internal, "errorinvaliddata" => CommunicationType::error_invalid_data, "errorinvaliduserid" => CommunicationType::error_invalid_user_id, "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, @@ -310,6 +316,8 @@ impl CommunicationType { "registerresponse" => CommunicationType::register_response, "identification" => CommunicationType::identification, "identificationresponse" => CommunicationType::identification_response, + "registeriota" => CommunicationType::register_iota, + "registeriotasuccess" => CommunicationType::register_iota_success, "ping" => CommunicationType::ping, "pong" => CommunicationType::pong, "addchat" => CommunicationType::add_chat, diff --git a/src/server/api.rs b/src/server/api.rs index 69077a4..9d77b5b 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -9,6 +9,7 @@ use crate::{ util::crypto_helper::public_key_to_base64, }; use axum::http::HeaderValue; +use base64::Engine as _; use http_body_util::Full; use hyper::body::Bytes; use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; @@ -112,29 +113,29 @@ pub async fn handle( } // get/id/ "id" => { - let username = path_parts[2]; - if username.is_empty() { - not_found() + if path_parts.len() != 4 { + bad_request() } else { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::success) + let username = path_parts[3]; + if username.is_empty() { + not_found() + } else { + if let Ok(( + id, + iota_id, + username, + _, + _, + _, + _, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + let cv = CommunicationValue::new(CommunicationType::success) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data( @@ -145,10 +146,6 @@ pub async fn handle( DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -156,18 +153,17 @@ pub async fn handle( .add_data( DataTypes::sub_end, JsonValue::Number(Number::from(sub_end)), - ) - .to_json() - .to_string(), - ) - } else { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + ); + (StatusCode::OK, "application/json", cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } } } } @@ -177,30 +173,30 @@ pub async fn handle( public_key_to_base64(&get_public_key()), ), "user" => { - let id = path_parts[2]; - let id: i64 = id.parse().unwrap_or(0); - if id == 0 { + if path_parts.len() != 4 { bad_request() } else { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::success) + let id = path_parts[3]; + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + let mut cv = CommunicationValue::new(CommunicationType::success) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data( @@ -211,10 +207,6 @@ pub async fn handle( DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -222,18 +214,32 @@ pub async fn handle( .add_data( DataTypes::sub_end, JsonValue::Number(Number::from(sub_end)), - ) - .to_json() - .to_string(), - ) - } else { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + ); + if let Some(display) = display { + cv = cv.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + cv = cv.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + cv = cv.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + cv = cv.add_data_str( + DataTypes::avatar, + base64::engine::general_purpose::STANDARD.encode(avatar), + ); + } + (StatusCode::OK, "application/json", cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } } } } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 98b6f00..80e50fc 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -186,7 +186,8 @@ impl OmikronConnection { self.send_message( &CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()), + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, JsonValue::Boolean(true)), ) .await; } else { @@ -311,10 +312,6 @@ impl OmikronConnection { DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -324,6 +321,20 @@ impl OmikronConnection { JsonValue::Number(Number::from(sub_end)), ); + if let Some(display) = display { + response = response.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + response = response.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + response = response.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + response = + response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); + } + let user_status = user_online_tracker::get_user_status(id).await; let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) @@ -386,10 +397,6 @@ impl OmikronConnection { DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -399,6 +406,20 @@ impl OmikronConnection { JsonValue::Number(Number::from(sub_end)), ); + if let Some(display) = display { + response = response.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + response = response.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + response = response.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + response = + response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); + } + let user_status = user_online_tracker::get_user_status(id).await; let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) @@ -570,23 +591,44 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::complete_register_iota) { - if let (Some(iota_id), Some(public_key)) = ( - cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), - ) { - match sql::register_complete_iota(iota_id, public_key.to_string()).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; + let iota_id_opt = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()); + + if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { + if let Some(iota_id) = iota_id_opt { + // Existing logic to update iota + match sql::register_complete_iota(iota_id, public_key.to_string()).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; + } else { + // New logic to create iota and return id + match sql::create_new_iota(public_key.to_string()).await { + Ok(new_iota_id) => { + let response = + CommunicationValue::new(CommunicationType::complete_register_iota) + .with_id(cv.get_id()) + .add_data(DataTypes::iota_id, JsonValue::from(new_iota_id)); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } } } } else { diff --git a/src/sql/sql.rs b/src/sql/sql.rs index bd3d283..abb9c8d 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -150,10 +150,10 @@ pub async fn get_by_username( i64, i64, String, - String, - String, - String, - String, + Option, + Option, + Option, + Option>, i32, i64, String, @@ -177,10 +177,10 @@ pub async fn get_by_username( let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); - let avatar: Vec = row.get("avatar"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key: String = row.get("public_key"); @@ -191,10 +191,10 @@ pub async fn get_by_username( id, iota_id, username, - String::from_utf8_lossy(&display).to_string(), - String::from_utf8_lossy(&status).to_string(), - String::from_utf8_lossy(&about).to_string(), - String::from_utf8_lossy(&avatar).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, sub_level, sub_end, public_key, @@ -213,10 +213,10 @@ pub async fn get_by_user_id( i64, i64, String, - String, - String, - String, - String, + Option, + Option, + Option, + Option>, i32, i64, String, @@ -240,10 +240,10 @@ pub async fn get_by_user_id( let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); - let avatar: Vec = row.get("avatar"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key: String = row.get("public_key"); @@ -254,10 +254,10 @@ pub async fn get_by_user_id( id, iota_id, username, - String::from_utf8_lossy(&display).to_string(), - String::from_utf8_lossy(&status).to_string(), - String::from_utf8_lossy(&about).to_string(), - String::from_utf8_lossy(&avatar).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, sub_level, sub_end, public_key, @@ -276,10 +276,10 @@ pub async fn get_users_by_iota_id( i64, i64, String, - String, - String, - String, - String, + Option, + Option, + Option, + Option>, i32, i64, String, @@ -303,10 +303,10 @@ pub async fn get_users_by_iota_id( let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); - let avatar: Vec = row.get("avatar"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key: String = row.get("public_key"); @@ -317,10 +317,10 @@ pub async fn get_users_by_iota_id( id, iota_id, username, - String::from_utf8_lossy(&display).to_string(), - String::from_utf8_lossy(&status).to_string(), - String::from_utf8_lossy(&about).to_string(), - String::from_utf8_lossy(&avatar).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, sub_level, sub_end, public_key, @@ -477,9 +477,9 @@ pub async fn print_users() -> Result<(), Box> { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); @@ -489,9 +489,9 @@ pub async fn print_users() -> Result<(), Box> { id, iota_id, username, - String::from_utf8_lossy(&display), - String::from_utf8_lossy(&status), - String::from_utf8_lossy(&about), + display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), + status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), + about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), sub_level, sub_end ) @@ -504,6 +504,12 @@ pub async fn print_users() -> Result<(), Box> { // ========================================================================================== // IOTA // ========================================================================================== +pub async fn create_new_iota(public_key: String) -> Result { + let new_id = get_register_id().await as i64; + register_complete_iota(new_id, public_key).await?; + Ok(new_id) +} + pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); From d51b2f73a2d2ef459a8fc04da0c72769b317b185 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:57:48 +0100 Subject: [PATCH 018/220] register process optional value handlin in the sql database --- src/data/communication.rs | 8 ++ src/server/api.rs | 158 ++++++++++++++++--------------- src/server/omikron_connection.rs | 92 +++++++++++++----- src/sql/sql.rs | 90 ++++++++++-------- 4 files changed, 205 insertions(+), 143 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 4dd6d43..de68e6b 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -10,6 +10,7 @@ pub enum DataTypes { error_type, accepted_ids, uuid, + register_id, settings, settings_name, chat_partner_id, @@ -92,6 +93,7 @@ impl DataTypes { match normalized.as_str() { "errortype" => DataTypes::error_type, "chatpartnerid" => DataTypes::chat_partner_id, + "registerid" => DataTypes::register_id, "uuid" => DataTypes::uuid, "settings" => DataTypes::settings, "settingsname" => DataTypes::settings_name, @@ -174,6 +176,7 @@ impl DataTypes { #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, + error_internal, error_invalid_data, error_invalid_user_id, error_invalid_omikron_id, @@ -211,6 +214,8 @@ pub enum CommunicationType { register_response, identification, identification_response, + register_iota, + register_iota_success, ping, pong, add_chat, @@ -273,6 +278,7 @@ impl CommunicationType { "function" => CommunicationType::function, "update" => CommunicationType::update, "createuser" => CommunicationType::create_user, + "errorinternal" => CommunicationType::error_internal, "errorinvaliddata" => CommunicationType::error_invalid_data, "errorinvaliduserid" => CommunicationType::error_invalid_user_id, "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, @@ -310,6 +316,8 @@ impl CommunicationType { "registerresponse" => CommunicationType::register_response, "identification" => CommunicationType::identification, "identificationresponse" => CommunicationType::identification_response, + "registeriota" => CommunicationType::register_iota, + "registeriotasuccess" => CommunicationType::register_iota_success, "ping" => CommunicationType::ping, "pong" => CommunicationType::pong, "addchat" => CommunicationType::add_chat, diff --git a/src/server/api.rs b/src/server/api.rs index 69077a4..9d77b5b 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -9,6 +9,7 @@ use crate::{ util::crypto_helper::public_key_to_base64, }; use axum::http::HeaderValue; +use base64::Engine as _; use http_body_util::Full; use hyper::body::Bytes; use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; @@ -112,29 +113,29 @@ pub async fn handle( } // get/id/ "id" => { - let username = path_parts[2]; - if username.is_empty() { - not_found() + if path_parts.len() != 4 { + bad_request() } else { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::success) + let username = path_parts[3]; + if username.is_empty() { + not_found() + } else { + if let Ok(( + id, + iota_id, + username, + _, + _, + _, + _, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + let cv = CommunicationValue::new(CommunicationType::success) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data( @@ -145,10 +146,6 @@ pub async fn handle( DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -156,18 +153,17 @@ pub async fn handle( .add_data( DataTypes::sub_end, JsonValue::Number(Number::from(sub_end)), - ) - .to_json() - .to_string(), - ) - } else { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + ); + (StatusCode::OK, "application/json", cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } } } } @@ -177,30 +173,30 @@ pub async fn handle( public_key_to_base64(&get_public_key()), ), "user" => { - let id = path_parts[2]; - let id: i64 = id.parse().unwrap_or(0); - if id == 0 { + if path_parts.len() != 4 { bad_request() } else { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::success) + let id = path_parts[3]; + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else { + if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + let mut cv = CommunicationValue::new(CommunicationType::success) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data( @@ -211,10 +207,6 @@ pub async fn handle( DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -222,18 +214,32 @@ pub async fn handle( .add_data( DataTypes::sub_end, JsonValue::Number(Number::from(sub_end)), - ) - .to_json() - .to_string(), - ) - } else { - ( - StatusCode::OK, - "application/json", - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + ); + if let Some(display) = display { + cv = cv.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + cv = cv.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + cv = cv.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + cv = cv.add_data_str( + DataTypes::avatar, + base64::engine::general_purpose::STANDARD.encode(avatar), + ); + } + (StatusCode::OK, "application/json", cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + "application/json", + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } } } } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 98b6f00..80e50fc 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -186,7 +186,8 @@ impl OmikronConnection { self.send_message( &CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()), + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, JsonValue::Boolean(true)), ) .await; } else { @@ -311,10 +312,6 @@ impl OmikronConnection { DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -324,6 +321,20 @@ impl OmikronConnection { JsonValue::Number(Number::from(sub_end)), ); + if let Some(display) = display { + response = response.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + response = response.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + response = response.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + response = + response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); + } + let user_status = user_online_tracker::get_user_status(id).await; let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) @@ -386,10 +397,6 @@ impl OmikronConnection { DataTypes::iota_id, JsonValue::Number(Number::from(iota_id)), ) - .add_data_str(DataTypes::display, display) - .add_data_str(DataTypes::status, status) - .add_data_str(DataTypes::about, about) - .add_data_str(DataTypes::avatar, avatar) .add_data( DataTypes::sub_level, JsonValue::Number(Number::from(sub_level)), @@ -399,6 +406,20 @@ impl OmikronConnection { JsonValue::Number(Number::from(sub_end)), ); + if let Some(display) = display { + response = response.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + response = response.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + response = response.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + response = + response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); + } + let user_status = user_online_tracker::get_user_status(id).await; let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) @@ -570,23 +591,44 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::complete_register_iota) { - if let (Some(iota_id), Some(public_key)) = ( - cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), - ) { - match sql::register_complete_iota(iota_id, public_key.to_string()).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; + let iota_id_opt = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()); + + if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { + if let Some(iota_id) = iota_id_opt { + // Existing logic to update iota + match sql::register_complete_iota(iota_id, public_key.to_string()).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; + } else { + // New logic to create iota and return id + match sql::create_new_iota(public_key.to_string()).await { + Ok(new_iota_id) => { + let response = + CommunicationValue::new(CommunicationType::complete_register_iota) + .with_id(cv.get_id()) + .add_data(DataTypes::iota_id, JsonValue::from(new_iota_id)); + self.send_message(&response).await; + } + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) + .await; + } } } } else { diff --git a/src/sql/sql.rs b/src/sql/sql.rs index bd3d283..abb9c8d 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -150,10 +150,10 @@ pub async fn get_by_username( i64, i64, String, - String, - String, - String, - String, + Option, + Option, + Option, + Option>, i32, i64, String, @@ -177,10 +177,10 @@ pub async fn get_by_username( let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); - let avatar: Vec = row.get("avatar"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key: String = row.get("public_key"); @@ -191,10 +191,10 @@ pub async fn get_by_username( id, iota_id, username, - String::from_utf8_lossy(&display).to_string(), - String::from_utf8_lossy(&status).to_string(), - String::from_utf8_lossy(&about).to_string(), - String::from_utf8_lossy(&avatar).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, sub_level, sub_end, public_key, @@ -213,10 +213,10 @@ pub async fn get_by_user_id( i64, i64, String, - String, - String, - String, - String, + Option, + Option, + Option, + Option>, i32, i64, String, @@ -240,10 +240,10 @@ pub async fn get_by_user_id( let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); - let avatar: Vec = row.get("avatar"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key: String = row.get("public_key"); @@ -254,10 +254,10 @@ pub async fn get_by_user_id( id, iota_id, username, - String::from_utf8_lossy(&display).to_string(), - String::from_utf8_lossy(&status).to_string(), - String::from_utf8_lossy(&about).to_string(), - String::from_utf8_lossy(&avatar).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, sub_level, sub_end, public_key, @@ -276,10 +276,10 @@ pub async fn get_users_by_iota_id( i64, i64, String, - String, - String, - String, - String, + Option, + Option, + Option, + Option>, i32, i64, String, @@ -303,10 +303,10 @@ pub async fn get_users_by_iota_id( let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); - let avatar: Vec = row.get("avatar"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key: String = row.get("public_key"); @@ -317,10 +317,10 @@ pub async fn get_users_by_iota_id( id, iota_id, username, - String::from_utf8_lossy(&display).to_string(), - String::from_utf8_lossy(&status).to_string(), - String::from_utf8_lossy(&about).to_string(), - String::from_utf8_lossy(&avatar).to_string(), + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, sub_level, sub_end, public_key, @@ -477,9 +477,9 @@ pub async fn print_users() -> Result<(), Box> { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); let username: String = row.get("username"); - let display: Vec = row.get("display"); - let status: Vec = row.get("status"); - let about: Vec = row.get("about"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); @@ -489,9 +489,9 @@ pub async fn print_users() -> Result<(), Box> { id, iota_id, username, - String::from_utf8_lossy(&display), - String::from_utf8_lossy(&status), - String::from_utf8_lossy(&about), + display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), + status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), + about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), sub_level, sub_end ) @@ -504,6 +504,12 @@ pub async fn print_users() -> Result<(), Box> { // ========================================================================================== // IOTA // ========================================================================================== +pub async fn create_new_iota(public_key: String) -> Result { + let new_id = get_register_id().await as i64; + register_complete_iota(new_id, public_key).await?; + Ok(new_id) +} + pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); From 67fa97b8e803d04fd9d60ed25e47ad00a8fa2580 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:22:42 +0100 Subject: [PATCH 019/220] iota auth --- src/server/omikron_connection.rs | 12 ++++--- src/sql/sql.rs | 56 +++++++++++++++++--------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 80e50fc..be110a4 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -305,6 +305,7 @@ impl OmikronConnection { { let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) @@ -390,6 +391,7 @@ impl OmikronConnection { { let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) @@ -463,16 +465,14 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::get_iota_data) { - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).cloned() { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id) { if let Some(iota_id) = iota_id.as_i64() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ); + .add_data(DataTypes::iota_id, JsonValue::from(iota_id)); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) @@ -501,6 +501,7 @@ impl OmikronConnection { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::public_key, public_key) .add_data( DataTypes::user_id, @@ -540,6 +541,7 @@ impl OmikronConnection { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::public_key, public_key) .add_data( DataTypes::user_id, diff --git a/src/sql/sql.rs b/src/sql/sql.rs index abb9c8d..d460f6c 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -229,7 +229,7 @@ pub async fn get_by_user_id( let pool = db_lock.as_ref().expect("Database pool is not initialized"); let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) @@ -292,7 +292,7 @@ pub async fn get_users_by_iota_id( let pool = db_lock.as_ref().expect("Database pool is not initialized"); let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", ) .bind(iota_id_param) .fetch_all(pool) @@ -336,7 +336,7 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET username = ? WHERE id = ?") + sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_username) .bind(id) .execute(pool) @@ -349,7 +349,7 @@ pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sql let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET display = ? WHERE id = ?") + sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_display) .bind(id) .execute(pool) @@ -362,7 +362,7 @@ pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Erro let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") + sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_avatar) .bind(id) .execute(pool) @@ -375,7 +375,7 @@ pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET about = ? WHERE id = ?") + sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_about) .bind(id) .execute(pool) @@ -388,7 +388,7 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET status = ? WHERE id = ?") + sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_status) .bind(id) .execute(pool) @@ -401,7 +401,7 @@ pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") + sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") .bind(new_iota_id) .bind(id) .execute(pool) @@ -418,12 +418,14 @@ pub async fn change_keys( let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key) - .bind(new_private_key_hash) - .bind(id) - .execute(pool) - .await?; + sqlx::query( + "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(pool) + .await?; Ok(()) } @@ -431,7 +433,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET token = ? WHERE id = ?") + sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_token) .bind(id) .execute(pool) @@ -527,7 +529,7 @@ pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("DELETE FROM users WHERE id = ?") + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") .bind(id) .execute(pool) .await?; @@ -539,19 +541,21 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") - .bind(id) - .fetch_optional(pool) - .await? - .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) - .ok_or_else(|| sqlx::Error::RowNotFound) + sqlx::query_as::<_, (i64, Vec)>( + "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(pool) + .await? + .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) + .ok_or_else(|| sqlx::Error::RowNotFound) } pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_key) .bind(id) .execute(pool) @@ -564,7 +568,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("DELETE FROM iotas WHERE id = ?") + sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") .bind(id) .execute(pool) .await?; @@ -598,7 +602,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> let pool = db_lock.as_ref().expect("Database pool is not initialized"); let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = ?", + "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) @@ -619,7 +623,7 @@ pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error let active = if active { 1 } else { 0 }; - sqlx::query("UPDATE omikrons SET active = ? WHERE id = ?") + sqlx::query("UPDATE omikrons SET active = ? WHERE id = CAST(? AS UNSIGNED)") .bind(active) .bind(id) .execute(pool) From 0d28d225c6d5eb8ff1bbf87d7dde7aecc18a4e5a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:22:42 +0100 Subject: [PATCH 020/220] iota auth --- src/server/omikron_connection.rs | 12 ++++--- src/sql/sql.rs | 56 +++++++++++++++++--------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 80e50fc..be110a4 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -305,6 +305,7 @@ impl OmikronConnection { { let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) @@ -390,6 +391,7 @@ impl OmikronConnection { { let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::username, username) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) @@ -463,16 +465,14 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::get_iota_data) { - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).cloned() { + if let Some(iota_id) = cv.get_data(DataTypes::iota_id) { if let Some(iota_id) = iota_id.as_i64() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ); + .add_data(DataTypes::iota_id, JsonValue::from(iota_id)); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) @@ -501,6 +501,7 @@ impl OmikronConnection { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::public_key, public_key) .add_data( DataTypes::user_id, @@ -540,6 +541,7 @@ impl OmikronConnection { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) .add_data_str(DataTypes::public_key, public_key) .add_data( DataTypes::user_id, diff --git a/src/sql/sql.rs b/src/sql/sql.rs index abb9c8d..d460f6c 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -229,7 +229,7 @@ pub async fn get_by_user_id( let pool = db_lock.as_ref().expect("Database pool is not initialized"); let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) @@ -292,7 +292,7 @@ pub async fn get_users_by_iota_id( let pool = db_lock.as_ref().expect("Database pool is not initialized"); let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", ) .bind(iota_id_param) .fetch_all(pool) @@ -336,7 +336,7 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET username = ? WHERE id = ?") + sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_username) .bind(id) .execute(pool) @@ -349,7 +349,7 @@ pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sql let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET display = ? WHERE id = ?") + sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_display) .bind(id) .execute(pool) @@ -362,7 +362,7 @@ pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Erro let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") + sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_avatar) .bind(id) .execute(pool) @@ -375,7 +375,7 @@ pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET about = ? WHERE id = ?") + sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_about) .bind(id) .execute(pool) @@ -388,7 +388,7 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET status = ? WHERE id = ?") + sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_status) .bind(id) .execute(pool) @@ -401,7 +401,7 @@ pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") + sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") .bind(new_iota_id) .bind(id) .execute(pool) @@ -418,12 +418,14 @@ pub async fn change_keys( let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key) - .bind(new_private_key_hash) - .bind(id) - .execute(pool) - .await?; + sqlx::query( + "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(pool) + .await?; Ok(()) } @@ -431,7 +433,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE users SET token = ? WHERE id = ?") + sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_token) .bind(id) .execute(pool) @@ -527,7 +529,7 @@ pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("DELETE FROM users WHERE id = ?") + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") .bind(id) .execute(pool) .await?; @@ -539,19 +541,21 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") - .bind(id) - .fetch_optional(pool) - .await? - .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) - .ok_or_else(|| sqlx::Error::RowNotFound) + sqlx::query_as::<_, (i64, Vec)>( + "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(pool) + .await? + .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) + .ok_or_else(|| sqlx::Error::RowNotFound) } pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") .bind(new_key) .bind(id) .execute(pool) @@ -564,7 +568,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("DELETE FROM iotas WHERE id = ?") + sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") .bind(id) .execute(pool) .await?; @@ -598,7 +602,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> let pool = db_lock.as_ref().expect("Database pool is not initialized"); let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = ?", + "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) @@ -619,7 +623,7 @@ pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error let active = if active { 1 } else { 0 }; - sqlx::query("UPDATE omikrons SET active = ? WHERE id = ?") + sqlx::query("UPDATE omikrons SET active = ? WHERE id = CAST(? AS UNSIGNED)") .bind(active) .bind(id) .execute(pool) From a00f3dd239c9171b5feed25fef1266dd6d9b91dd Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:21:05 +0100 Subject: [PATCH 021/220] User Auth / iota auth --- src/sql/sql.rs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index d460f6c..ec0f1d2 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -445,22 +445,22 @@ pub async fn register_complete_user( id: i64, username: String, public_key: String, - private_key_hash: String, iota_id: i64, token: String, ) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, token) VALUES (?, ?, ?, ?, ?, ?)") - .bind(id) - .bind(username) - .bind(public_key) - .bind(private_key_hash) - .bind(iota_id) - .bind(token) - .execute(pool) - .await?; + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id) + .bind(username) + .bind(public_key) + .bind(iota_id) + .bind(token) + .execute(pool) + .await?; Ok(()) } @@ -541,14 +541,23 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, Vec)>( + let result = sqlx::query_as::<_, (u64, Vec)>( "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) - .await? - .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) - .ok_or_else(|| sqlx::Error::RowNotFound) + .await; + + match result { + Ok(optional_row) => match optional_row { + Some((id_u64, public_key)) => Ok(( + id_u64 as i64, + String::from_utf8_lossy(&public_key).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } } pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { From 3fa1c69762b827a283d991c7364c3aeb66ecb502 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:21:05 +0100 Subject: [PATCH 022/220] User Auth / iota auth --- src/sql/sql.rs | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index d460f6c..ec0f1d2 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -445,22 +445,22 @@ pub async fn register_complete_user( id: i64, username: String, public_key: String, - private_key_hash: String, iota_id: i64, token: String, ) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, token) VALUES (?, ?, ?, ?, ?, ?)") - .bind(id) - .bind(username) - .bind(public_key) - .bind(private_key_hash) - .bind(iota_id) - .bind(token) - .execute(pool) - .await?; + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id) + .bind(username) + .bind(public_key) + .bind(iota_id) + .bind(token) + .execute(pool) + .await?; Ok(()) } @@ -541,14 +541,23 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, Vec)>( + let result = sqlx::query_as::<_, (u64, Vec)>( "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) - .await? - .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) - .ok_or_else(|| sqlx::Error::RowNotFound) + .await; + + match result { + Ok(optional_row) => match optional_row { + Some((id_u64, public_key)) => Ok(( + id_u64 as i64, + String::from_utf8_lossy(&public_key).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } } pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { From 6053d4fbdee836aea2b7c76cfdfc40bcf9db35bc Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:21:05 +0100 Subject: [PATCH 023/220] User Auth / iota auth --- src/server/omikron_connection.rs | 4 +--- src/sql/sql.rs | 37 ++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index be110a4..e86eb65 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -651,14 +651,12 @@ impl OmikronConnection { cv.get_data(DataTypes::username).and_then(|v| v.as_str()), cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), // Assuming reset_token is sent as token + cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), ) { - // The documentation does not specify a private_key_hash, using an empty string. match sql::register_complete_user( user_id, username.to_string(), public_key.to_string(), - "".to_string(), // private_key_hash iota_id, reset_token.to_string(), ) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index d460f6c..ec0f1d2 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -445,22 +445,22 @@ pub async fn register_complete_user( id: i64, username: String, public_key: String, - private_key_hash: String, iota_id: i64, token: String, ) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, token) VALUES (?, ?, ?, ?, ?, ?)") - .bind(id) - .bind(username) - .bind(public_key) - .bind(private_key_hash) - .bind(iota_id) - .bind(token) - .execute(pool) - .await?; + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id) + .bind(username) + .bind(public_key) + .bind(iota_id) + .bind(token) + .execute(pool) + .await?; Ok(()) } @@ -541,14 +541,23 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, Vec)>( + let result = sqlx::query_as::<_, (u64, Vec)>( "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) - .await? - .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) - .ok_or_else(|| sqlx::Error::RowNotFound) + .await; + + match result { + Ok(optional_row) => match optional_row { + Some((id_u64, public_key)) => Ok(( + id_u64 as i64, + String::from_utf8_lossy(&public_key).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } } pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { From 2bcf6e07e69eafc755f77c0d79f090654313ba12 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 14 Jan 2026 22:21:05 +0100 Subject: [PATCH 024/220] User Auth / iota auth --- src/server/omikron_connection.rs | 4 +--- src/sql/sql.rs | 37 ++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index be110a4..e86eb65 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -651,14 +651,12 @@ impl OmikronConnection { cv.get_data(DataTypes::username).and_then(|v| v.as_str()), cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), // Assuming reset_token is sent as token + cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), ) { - // The documentation does not specify a private_key_hash, using an empty string. match sql::register_complete_user( user_id, username.to_string(), public_key.to_string(), - "".to_string(), // private_key_hash iota_id, reset_token.to_string(), ) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index d460f6c..ec0f1d2 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -445,22 +445,22 @@ pub async fn register_complete_user( id: i64, username: String, public_key: String, - private_key_hash: String, iota_id: i64, token: String, ) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query("INSERT INTO users (id, username, public_key, private_key_hash, iota_id, token) VALUES (?, ?, ?, ?, ?, ?)") - .bind(id) - .bind(username) - .bind(public_key) - .bind(private_key_hash) - .bind(iota_id) - .bind(token) - .execute(pool) - .await?; + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id) + .bind(username) + .bind(public_key) + .bind(iota_id) + .bind(token) + .execute(pool) + .await?; Ok(()) } @@ -541,14 +541,23 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); - sqlx::query_as::<_, (i64, Vec)>( + let result = sqlx::query_as::<_, (u64, Vec)>( "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", ) .bind(id) .fetch_optional(pool) - .await? - .map(|(id, public_key)| (id, String::from_utf8_lossy(&public_key).to_string())) - .ok_or_else(|| sqlx::Error::RowNotFound) + .await; + + match result { + Ok(optional_row) => match optional_row { + Some((id_u64, public_key)) => Ok(( + id_u64 as i64, + String::from_utf8_lossy(&public_key).to_string(), + )), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } } pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { From 7a16d6b48344d892872cd0bb14ae2d4463d64be4 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Sun, 18 Jan 2026 21:48:21 +0100 Subject: [PATCH 025/220] Client auth, Encryption module --- src/data/communication.rs | 17 +- src/server/omikron_connection.rs | 14 + src/util/crypto_util.rs | 1004 ++++++++++++++++++++++++++++++ 3 files changed, 1029 insertions(+), 6 deletions(-) create mode 100644 src/util/crypto_util.rs diff --git a/src/data/communication.rs b/src/data/communication.rs index de68e6b..b2b5898 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -250,6 +250,7 @@ pub enum CommunicationType { get_user_data, get_iota_data, + iota_user_data, change_user_data, change_iota_data, @@ -342,6 +343,7 @@ impl CommunicationType { "getuserdata" => CommunicationType::get_user_data, "getiotadata" => CommunicationType::get_iota_data, + "iotauserdata" => CommunicationType::iota_user_data, "changeuserdata" => CommunicationType::change_user_data, "changeiotadata" => CommunicationType::change_iota_data, @@ -362,11 +364,11 @@ impl CommunicationType { #[derive(Debug, Clone)] pub struct CommunicationValue { - pub id: Uuid, - pub comm_type: CommunicationType, - pub sender: i64, - pub receiver: i64, - pub data: HashMap, + id: Uuid, + comm_type: CommunicationType, + sender: i64, + receiver: i64, + data: HashMap, } #[allow(dead_code)] @@ -421,7 +423,10 @@ impl CommunicationValue { self.data.get(&key) } - pub(crate) fn is_type(&self, p0: CommunicationType) -> bool { + pub fn get_type(&self) -> CommunicationType { + self.comm_type.clone() + } + pub fn is_type(&self, p0: CommunicationType) -> bool { self.comm_type == p0 } pub fn to_json(&self) -> JsonValue { diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e86eb65..b2ee2c7 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -228,11 +228,15 @@ impl OmikronConnection { } return; } + if cv.is_type(CommunicationType::iota_connected) { + log_in!(PrintType::Omega, "IOTA connected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + let mut user_ids = JsonValue::Array(Vec::new()); if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { for user in users { + let _ = user_ids.push(JsonValue::from(user.0)); user_online_tracker::track_user_status( user.0, ConnectionType::UserOffline, @@ -240,7 +244,17 @@ impl OmikronConnection { ) .await; } + } else { + log_in!(PrintType::General, "SQL error, when loading users for IOTA"); } + self.send_message( + &CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, user_ids), + ) + .await; + } else { + log_in!(PrintType::General, "No IOTA ID found"); } return; } diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs new file mode 100644 index 0000000..8b6768f --- /dev/null +++ b/src/util/crypto_util.rs @@ -0,0 +1,1004 @@ +version = "0.1.0" +dependencies = [ + "aes", + "aes-gcm", + "ansi_term", + "async-tungstenite", + "axum", + "base64 0.22.1", + "block-modes", + "bytes", + "cbc", + "chacha20poly1305", + "chrono", + "cmake", + "crossterm", + "futures-util", + "hex", + "hkdf", + "http 1.4.0", + "hyper", + "json", + +[[package]] +name = "block-modes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2211b0817f061502a8dd9f11a37e879e79763e3c698d2418cf824d8cb2f21e" + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" + "crypto-common", + "inout", + "zeroize", +] + +[[package]] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +aes-gcm = "0.10.3" +tokio-native-tls = "0.3.1" +hkdf = "0.12.4" +chacha20poly1305 = "0.10.1" +block-modes = "0.9.1" +cbc = "0.1.2" +aes = "0.8.4" + + get_user_data, + get_iota_data, + iota_user_data, + + change_user_data, + change_iota_data, + "getuserdata" => CommunicationType::get_user_data, + "getiotadata" => CommunicationType::get_iota_data, + "iotauserdata" => CommunicationType::iota_user_data, + + "changeuserdata" => CommunicationType::change_user_data, + "changeiotadata" => CommunicationType::change_iota_data, +#[derive(Debug, Clone)] +pub struct CommunicationValue { + pub id: Uuid, + pub comm_type: CommunicationType, + pub sender: i64, + pub receiver: i64, + pub data: HashMap, + id: Uuid, + comm_type: CommunicationType, + sender: i64, + receiver: i64, + data: HashMap, +} + +#[allow(dead_code)] + } + + pub(crate) fn is_type(&self, p0: CommunicationType) -> bool { + pub fn get_type(&self) -> CommunicationType { + self.comm_type.clone() + } + pub fn is_type(&self, p0: CommunicationType) -> bool { + self.comm_type == p0 + } + pub fn to_json(&self) -> JsonValue { +use json::{JsonValue, number::Number}; +use once_cell::sync::Lazy; +use std::{ + collections::HashMap, + env, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Duration, +}; +use std::{collections::HashMap, env, sync::Arc, time::Duration}; +use tokio::{ + net::TcpStream, + sync::{Mutex, RwLock, mpsc}, + get_private_key, log, log_in, log_out, + rho::rho_manager::{self, RHO_CONNECTIONS}, + util::crypto_helper::{decrypt, load_public_key}, + util::crypto_helper::{decrypt_b64, secret_key_to_base64}, + util::logger::PrintType, +}; +use crate::{log_err, util::crypto_helper::secret_key_to_base64}; +use crate::{log_err, util::crypto_helper::load_public_key}; + +pub static WAITING_TASKS: Lazy< + DashMap, CommunicationValue) -> bool + Send + Sync>>, +> = Lazy::new(DashMap::new); + +/// Flag to ensure the connection loop is only started once. +static CONNECTION_LOOP_STARTED: AtomicBool = AtomicBool::new(false); + +static GENERIC_TASK: Lazy< + Mutex, CommunicationValue) -> bool + Send + Sync>>>, +> = Lazy::new(|| Mutex::new(None)); + +static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { + let conn = Arc::new(OmegaConnection::new()); + if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { + let conn_clone = conn.clone(); + tokio::spawn(async move { + conn_clone.connect_internal(0).await; + }); + } + let conn_clone = conn.clone(); + tokio::spawn(async move { + conn_clone.connect_internal(0).await; + }); + conn +}); + + } + pub fn connect(self: Arc) { + if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { + let cloned_self = self.clone(); + tokio::spawn(async move { + cloned_self.connect_internal(0).await; + }); + } + let cloned_self = self.clone(); + tokio::spawn(async move { + cloned_self.connect_internal(0).await; + }); + } + async fn connect_internal(self: Arc, mut retry: usize) { + loop { + })?; + + let server_pub_key_obj = load_public_key(server_pub_key).ok_or("Failed to load public key".to_string())?; + let server_pub_key_obj = load_public_key(server_pub_key).unwrap(); + + let decrypted_challenge = decrypt( + get_private_key(), + server_pub_key_obj, + let decrypted_challenge = decrypt_b64( + &secret_key_to_base64(&get_private_key()), + server_pub_key, + challenge, + ) + .map_err(|e| { + } + + if let Some(accepted) = final_cv.get_data(DataTypes::accepted).and_then(|v| v.as_bool()) { + if !accepted { + log_err!(PrintType::Omega, "Omega did not accept identification."); + return false; + } + } else { + log_err!(PrintType::Omega, "Omega response did not contain 'accepted' field."); + return false; + } + + tokio::spawn(async move { + let mut connected_iota_ids: Vec = Vec::new(); + let mut connected_user_ids: Vec = Vec::new(); + } + let msg_id = cv.get_id(); + log_in!(PrintType::Omikron, "{}", &cv.to_json().to_string()); + log_in!(PrintType::Omega, "{}", &cv.to_json().to_string()); + // Handle waiting tasks + if let Some(task) = WAITING_TASKS.remove(&msg_id) { + if (task.1)(self.clone(), cv.clone()) { + // continue in the read_loop + continue; + } + } else { + // Handle generic task + let generic_task_option = GENERIC_TASK.lock().await; + if let Some(generic_task) = generic_task_option.as_ref() { + if generic_task(self.clone(), cv.clone()) { + // continue in the read_loop + } + } + } + } + #[allow(non_snake_case)] + Some(Ok(Message::Close(_))) | None => break, + Some(Ok(Message::Close(_))) | None => continue, + Some(Err(_)) => break, + _ => {} + } +use async_tungstenite::tungstenite::Message; +use async_tungstenite::{WebSocketReceiver, WebSocketSender}; +use json::JsonValue; +use json::number::Number; +use rand::Rng; +use rand::distributions::Alphanumeric; +use std::sync::{Arc, Weak}; +use std::time::Duration; +use tokio::sync::RwLock; +use tokio_util::compat::Compat; +use tungstenite::Utf8Bytes; +use crate::calls::call_manager; +use crate::omega::omega_connection::{WAITING_TASKS, get_omega_connection}; +use crate::util::crypto_helper::{load_public_key, public_key_to_base64}; +use crate::util::crypto_util::{DataFormat, SecurePayload}; +use crate::util::logger::PrintType; +use crate::{ + // calls::call_manager::CallManager, + omega::omega_connection::OmegaConnection, +}; +use crate::{log_in, log_out}; +use crate::{get_private_key, get_public_key, log_in, log_out}; + +/// ClientConnection represents a WebSocket connection from a client device +pub struct ClientConnection { + /// WebSocket session + pub sender: Arc>>>, + pub receiver: Arc>>>, + /// User ID associated with this client + pub user_id: Arc>, + /// Whether this connection has been identified/authenticated + pub identified: Arc>, + /// Ping latency tracking + identified: Arc>, + challenged: Arc>, + challenge: Arc>, + pub ping: Arc>, + /// Weak reference to RhoConnection to avoid circular references + pub rho_connection: Arc>>>, + /// List of user IDs this client is interested in receiving updates about + pub_key: Arc>>>, + pub rho_connection: Arc>>>, + pub interested_users: Arc>>, +} + + user_id: Arc::new(RwLock::new(0)), + identified: Arc::new(RwLock::new(false)), + challenged: Arc::new(RwLock::new(false)), + challenge: Arc::new(RwLock::new(String::new())), + ping: Arc::new(RwLock::new(-1)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: Arc::new(RwLock::new(None)), + interested_users: Arc::new(RwLock::new(Vec::new())), + }) + } + + /// 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 + } + self.rho_connection.read().await.clone() + } + + /// Send a string message to the client + tokio::spawn(async move { + let cv = CommunicationValue::from_json(&message); + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_in!(PrintType::Client, "{}", &cv.to_json().to_string()); + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + // Handle identification + if cv.is_type(CommunicationType::identification) && !self.is_identified().await { + self.handle_identification(Arc::clone(&self), cv).await; + if !identified && cv.is_type(CommunicationType::identification) { + let user_id = cv + .get_data(DataTypes::user_id) + .and_then(|v| v.as_i64()) + .unwrap_or(0); + if user_id == 0 { + log_out!(PrintType::Client, "Invalid USER ID"); + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + self.close().await; + return; + } + + *self.user_id.write().await = user_id; + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, JsonValue::from(user_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + if let Ok(response_cv) = response_cv { + if !response_cv.is_type(CommunicationType::get_user_data) { + self.send_error_response(&cv.get_id(), CommunicationType::error_internal) + .await; + self.close().await; + return; + } + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + self.close().await; + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + + let encrypted_challenge = + SecurePayload::new(challenge, DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + *self.identified.write().await = true; + + let challenge_msg = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data_str( + DataTypes::public_key, + public_key_to_base64(&get_public_key()), + ) + .add_data_str(DataTypes::challenge, encrypted_challenge); + + self.send_message(&challenge_msg).await; + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_internal) + .await; + self.close().await; + return; + } + + return; + } + + if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { + let client_response = cv + .get_data(DataTypes::challenge) + .and_then(|v| v.as_str()) + .unwrap_or(""); + + if client_response == *self.challenge.read().await { + *self.challenged.write().await = true; + + let user_id = self.get_user_id().await; + + 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_no_iota, + ) + .await; + return; + } + }; + + // Set identification data + { + let mut user_id_guard = self.user_id.write().await; + *user_id_guard = user_id; + } + { + let mut identified_guard = self.identified.write().await; + *identified_guard = true; + } + *self.rho_connection.write().await = Some(Arc::clone(&rho_connection)); + + let response = + CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()); + self.send_message(&response).await; + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + self.close().await; + return; + } + return; + } + + if !self.is_identified().await { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) + .await; + self.close().await; + return; + } + + } + + /// Handle identification message + async fn handle_identification(&self, sarc: Arc, cv: CommunicationValue) { + // Extract user ID + let user_id: i64 = match cv.get_data(DataTypes::user_id) { + Some(id_str) => id_str.as_i64().unwrap_or(0), + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id) + .await; + return; + } + }; + + // Validate private key + if let Some(private_key_hash) = cv.get_data(DataTypes::private_key_hash) { + println!("private_key_hash: {}", private_key_hash); + let is_valid = true; // NO VALIDATION, + // SWAP TO AUTH VIA CHALLENGE + // auth_connector::is_private_key_valid(user_id, &private_key_hash.to_string()).await; + + if !is_valid { + println!("Invalid private key"); + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_private_key, + ) + .await; + return; + } + } else { + log_in!(PrintType::Client, "Missing private key"); + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_private_key) + .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_no_iota) + .await; + return; + } + }; + + // Set identification data + { + let mut user_id_guard = self.user_id.write().await; + *user_id_guard = user_id; + } + { + let mut identified_guard = self.identified.write().await; + *identified_guard = true; + } + + self.set_rho_connection(Arc::downgrade(&rho_connection)) + .await; + + rho_connection.add_client_connection(Arc::from(sarc)).await; + + 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, cv: CommunicationValue) { + // Update our ping if provided + user_id: Arc::clone(&self.user_id), + identified: Arc::clone(&self.identified), + challenged: Arc::clone(&self.challenged), + challenge: Arc::clone(&self.challenge), + ping: Arc::clone(&self.ping), + pub_key: Arc::clone(&self.pub_key), + rho_connection: Arc::clone(&self.rho_connection), + interested_users: Arc::clone(&self.interested_users), + } +use crate::util::crypto_helper::load_public_key; +use crate::util::crypto_helper::public_key_to_base64; +use crate::util::crypto_util::DataFormat; +use crate::util::crypto_util::SecurePayload; +use crate::util::logger::PrintType; +use async_tungstenite::WebSocketReceiver; +use async_tungstenite::WebSocketSender; +}; +use tokio::sync::RwLock; +use tokio::sync::mpsc; +use tokio_util::compat::Compat; +use tungstenite::Utf8Bytes; +use uuid::Uuid; +use warp::filters::method::get; +use x448::PublicKey; + +use super::{rho_connection::RhoConnection, rho_manager}; + } + + log_in!(PrintType::Iota, "{}", cv.to_json().to_string()); + + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + .unwrap_or(0); + if iota_id == 0 { + log_out!(PrintType::Iota, "Invalid IOTA ID"); + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + self.close().await; + } + + let user_ids_json = cv + .get_data(DataTypes::user_ids) + .unwrap_or(&JsonValue::Null) + .clone(); + let mut user_ids = Vec::new(); + if let JsonValue::Array(ids) = user_ids_json { + for id_val in ids { + if let Some(id) = id_val.as_i64() { + user_ids.push(id); + } + } + } + + *self.iota_id.write().await = iota_id; + *self.user_ids.write().await = user_ids; + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) + + let encrypted_challenge = + encrypt(get_private_key(), pub_key, &challenge).unwrap_or_default(); + SecurePayload::new(&challenge, DataFormat::Base64, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + *self.identified.write().await = true; + + + let iota_id = self.get_iota_id().await; + let user_ids = self.get_user_ids().await; + + let mut validated_user_ids: Vec = Vec::new(); + for user_id in user_ids { + validated_user_ids.push(user_id); + } + + if rho_manager::contains_iota(iota_id).await { + if let Some(existing_rho) = rho_manager::get_rho_by_iota(iota_id).await { + } + + // Inform Omega & Verify Users + let iota_users_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::iota_connected).add_data( + DataTypes::iota_id, + JsonValue::from(self.get_iota_id().await), + ), + Some(Duration::from_secs(20)), + ) + .await; + + let mut user_ids: Vec = Vec::new(); + if let Ok(iota_users_cv) = iota_users_cv { + if !iota_users_cv.is_type(CommunicationType::iota_user_data) { + log_err!( + PrintType::Omikron, + "Invalid communication type {:?}", + iota_users_cv.get_type() + ); + return; + } + let val_user_ids = iota_users_cv.get_data(DataTypes::user_ids).unwrap().clone(); + + match val_user_ids { + JsonValue::Array(arr) => { + for item in arr { + if let JsonValue::Number(_) = item { + user_ids.push(item.as_i64().unwrap_or(0)); + } + } + } + _ => {} + } + } else { + log_err!(PrintType::Omikron, "Failed to retrieve user IDs"); + } + log_in!(PrintType::General, "User IDs: {:?}", user_ids.clone()); + + *self.user_ids.write().await = user_ids.clone(); + let rho_connection = + Arc::new(RhoConnection::new(self.clone(), validated_user_ids.clone()).await); + Arc::new(RhoConnection::new(self.clone(), user_ids.clone()).await); + + self.set_rho_connection(Arc::downgrade(&rho_connection)) + .await; + + let mut str = String::new(); + for id in &validated_user_ids { + for id in &user_ids { + str.push_str(&format!(",{}", id)); + } + if !str.is_empty() { + .with_id(cv.get_id()) + .add_data_str(DataTypes::accepted_ids, str) + .add_data_str(DataTypes::accepted, validated_user_ids.len().to_string()), + .add_data_str(DataTypes::accepted, user_ids.len().to_string()), + ) + .await; + } else { + } + } + pub async fn await_response( + &self, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + let task_tx = tx.clone(); + self.waiting_tasks.insert( + msg_id, + Box::new(move |_, response_cv| { + let inner_tx = task_tx.clone(); + tokio::spawn(async move { + if let Err(e) = inner_tx.send(response_cv).await { + log_err!( + PrintType::Iota, + "Failed to send response back to awaiter: {}", + e + ); + } + }); + true + }), + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(None) => Err("Failed to receive response, channel was closed.".to_string()), + Err(_) => { + self.waiting_tasks.remove(&msg_id); + Err(format!( + "Request timed out after {} seconds.", + timeout.as_secs() + )) + } + } + } +} + +impl std::fmt::Debug for IotaConnection { + }; + + // Notify OmegaConnection about the new Iota + OmegaConnection::connect_iota(rho_connection.get_iota_id().await, user_ids).await; + + rho_connection + } + + let connections = self.client_connections.read().await; + for connection in connections.iter() { + if connection.get_user_id().await == cv.receiver { + if connection.get_user_id().await == cv.get_receiver() { + connection.send_message(&cv).await; + } + } +pub mod config_util; +pub mod crypto_helper; +pub mod crypto_util; +pub mod file_util; +pub mod logger; + +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, Payload}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; +use hkdf::Hkdf; +use sha2::{Digest, Sha256}; +use std::fmt; +use x448::{PublicKey, Secret}; + +// --- Custom Errors --- +#[derive(Debug)] +pub enum SecurePayloadError { + InvalidBase64, + InvalidHex, + EncryptionError, + DecryptionError, + InvalidKeyLength, +} + +// --- Data Format Enum --- +#[derive(Clone, Copy, Debug)] +pub enum DataFormat { + Raw, + Base64, + Hex, +} + +// --- Main Class Structure --- +pub struct SecurePayload { + /// The internal canonical representation is always raw bytes. + inner_data: Vec, + /// The private key of the user associated with this payload instance. + private_key: Secret, +} + +impl Clone for SecurePayload { + fn clone(&self) -> Self { + Self { + inner_data: self.inner_data.clone(), + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + } + } +} + +impl SecurePayload { + /// Clear Constructor: Takes data in any format and the user's private key. + pub fn new>( + data: T, + format: DataFormat, + private_key: S, + ) -> Result + where + S: Into, + { + let raw_data = match format { + DataFormat::Raw => data.as_ref().to_vec(), + DataFormat::Base64 => BASE64_STD + .decode(data.as_ref()) + .map_err(|_| SecurePayloadError::InvalidBase64)?, + DataFormat::Hex => { + hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? + } + }; + + Ok(Self { + inner_data: raw_data, + private_key: private_key.into(), + }) + } + + /// Helper to get the public key associated with this instance's private key. + pub fn get_public_key(&self) -> [u8; 56] { + *PublicKey::from(&self.private_key).as_bytes() + } + + /// Exports the internal data to the requested format + pub fn export(&self, format: DataFormat) -> String { + match format.into() { + DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), + DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), + DataFormat::Hex => hex::encode(&self.inner_data), + } + } + + /// Access raw bytes directly + pub fn get_bytes(&self) -> &[u8] { + &self.inner_data + } + + /// Returns the SHA-256 Hash of the data in the requested format + pub fn get_hash(&self, format: DataFormat) -> String { + let mut hasher = Sha256::new(); + hasher.update(&self.inner_data); + let result = hasher.finalize(); + + match format { + DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), + DataFormat::Base64 => BASE64_STD.encode(result), + DataFormat::Hex => hex::encode(result), + } + } + + /// Encrypts the held data for a specific recipient using AES-256-GCM. + /// The message will contain ONLY the ciphertext. + pub fn encrypt_x448(&self, public_key: S) -> Result + where + S: Into, + { + let peer_pub = public_key.into(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + println!( + "Encryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 3. Key & Nonce Derivation (HKDF) + // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 4. Encrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let ciphertext = cipher + .encrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + // 5. Result is ONLY the ciphertext. No key or nonce is packed. + Ok(SecurePayload { + inner_data: ciphertext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } + + /// Decrypts the held data providing the sender's public key manually. + pub fn decrypt_to_format( + &self, + peer_public_key_bytes: &[u8; 56], + output_format: DataFormat, + ) -> Result { + let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; + Ok(decrypted_instance.export(output_format)) + } + + /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. + pub fn decrypt_x448( + &self, + peer_public_key_bytes: &[u8; 56], + ) -> Result { + // 1. Perform Exchange + let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + // LOGGING: Shared Secret + println!( + "Decryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 2. Key & Nonce Derivation (Must match encryption exactly) + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 3. Decrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let plaintext = cipher + .decrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + Ok(SecurePayload { + inner_data: plaintext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } +} From be42cb1235cf4876ecb5f98f90a647425c768282 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Sun, 18 Jan 2026 21:48:21 +0100 Subject: [PATCH 026/220] Client auth, Encryption module --- src/data/communication.rs | 17 +- src/server/omikron_connection.rs | 14 + src/util/crypto_util.rs | 1004 ++++++++++++++++++++++++++++++ 3 files changed, 1029 insertions(+), 6 deletions(-) create mode 100644 src/util/crypto_util.rs diff --git a/src/data/communication.rs b/src/data/communication.rs index de68e6b..b2b5898 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -250,6 +250,7 @@ pub enum CommunicationType { get_user_data, get_iota_data, + iota_user_data, change_user_data, change_iota_data, @@ -342,6 +343,7 @@ impl CommunicationType { "getuserdata" => CommunicationType::get_user_data, "getiotadata" => CommunicationType::get_iota_data, + "iotauserdata" => CommunicationType::iota_user_data, "changeuserdata" => CommunicationType::change_user_data, "changeiotadata" => CommunicationType::change_iota_data, @@ -362,11 +364,11 @@ impl CommunicationType { #[derive(Debug, Clone)] pub struct CommunicationValue { - pub id: Uuid, - pub comm_type: CommunicationType, - pub sender: i64, - pub receiver: i64, - pub data: HashMap, + id: Uuid, + comm_type: CommunicationType, + sender: i64, + receiver: i64, + data: HashMap, } #[allow(dead_code)] @@ -421,7 +423,10 @@ impl CommunicationValue { self.data.get(&key) } - pub(crate) fn is_type(&self, p0: CommunicationType) -> bool { + pub fn get_type(&self) -> CommunicationType { + self.comm_type.clone() + } + pub fn is_type(&self, p0: CommunicationType) -> bool { self.comm_type == p0 } pub fn to_json(&self) -> JsonValue { diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e86eb65..b2ee2c7 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -228,11 +228,15 @@ impl OmikronConnection { } return; } + if cv.is_type(CommunicationType::iota_connected) { + log_in!(PrintType::Omega, "IOTA connected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + let mut user_ids = JsonValue::Array(Vec::new()); if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { for user in users { + let _ = user_ids.push(JsonValue::from(user.0)); user_online_tracker::track_user_status( user.0, ConnectionType::UserOffline, @@ -240,7 +244,17 @@ impl OmikronConnection { ) .await; } + } else { + log_in!(PrintType::General, "SQL error, when loading users for IOTA"); } + self.send_message( + &CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, user_ids), + ) + .await; + } else { + log_in!(PrintType::General, "No IOTA ID found"); } return; } diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs new file mode 100644 index 0000000..8b6768f --- /dev/null +++ b/src/util/crypto_util.rs @@ -0,0 +1,1004 @@ +version = "0.1.0" +dependencies = [ + "aes", + "aes-gcm", + "ansi_term", + "async-tungstenite", + "axum", + "base64 0.22.1", + "block-modes", + "bytes", + "cbc", + "chacha20poly1305", + "chrono", + "cmake", + "crossterm", + "futures-util", + "hex", + "hkdf", + "http 1.4.0", + "hyper", + "json", + +[[package]] +name = "block-modes" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2211b0817f061502a8dd9f11a37e879e79763e3c698d2418cf824d8cb2f21e" + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" + "crypto-common", + "inout", + "zeroize", +] + +[[package]] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +aes-gcm = "0.10.3" +tokio-native-tls = "0.3.1" +hkdf = "0.12.4" +chacha20poly1305 = "0.10.1" +block-modes = "0.9.1" +cbc = "0.1.2" +aes = "0.8.4" + + get_user_data, + get_iota_data, + iota_user_data, + + change_user_data, + change_iota_data, + "getuserdata" => CommunicationType::get_user_data, + "getiotadata" => CommunicationType::get_iota_data, + "iotauserdata" => CommunicationType::iota_user_data, + + "changeuserdata" => CommunicationType::change_user_data, + "changeiotadata" => CommunicationType::change_iota_data, +#[derive(Debug, Clone)] +pub struct CommunicationValue { + pub id: Uuid, + pub comm_type: CommunicationType, + pub sender: i64, + pub receiver: i64, + pub data: HashMap, + id: Uuid, + comm_type: CommunicationType, + sender: i64, + receiver: i64, + data: HashMap, +} + +#[allow(dead_code)] + } + + pub(crate) fn is_type(&self, p0: CommunicationType) -> bool { + pub fn get_type(&self) -> CommunicationType { + self.comm_type.clone() + } + pub fn is_type(&self, p0: CommunicationType) -> bool { + self.comm_type == p0 + } + pub fn to_json(&self) -> JsonValue { +use json::{JsonValue, number::Number}; +use once_cell::sync::Lazy; +use std::{ + collections::HashMap, + env, + sync::{ + Arc, + atomic::{AtomicBool, Ordering}, + }, + time::Duration, +}; +use std::{collections::HashMap, env, sync::Arc, time::Duration}; +use tokio::{ + net::TcpStream, + sync::{Mutex, RwLock, mpsc}, + get_private_key, log, log_in, log_out, + rho::rho_manager::{self, RHO_CONNECTIONS}, + util::crypto_helper::{decrypt, load_public_key}, + util::crypto_helper::{decrypt_b64, secret_key_to_base64}, + util::logger::PrintType, +}; +use crate::{log_err, util::crypto_helper::secret_key_to_base64}; +use crate::{log_err, util::crypto_helper::load_public_key}; + +pub static WAITING_TASKS: Lazy< + DashMap, CommunicationValue) -> bool + Send + Sync>>, +> = Lazy::new(DashMap::new); + +/// Flag to ensure the connection loop is only started once. +static CONNECTION_LOOP_STARTED: AtomicBool = AtomicBool::new(false); + +static GENERIC_TASK: Lazy< + Mutex, CommunicationValue) -> bool + Send + Sync>>>, +> = Lazy::new(|| Mutex::new(None)); + +static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { + let conn = Arc::new(OmegaConnection::new()); + if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { + let conn_clone = conn.clone(); + tokio::spawn(async move { + conn_clone.connect_internal(0).await; + }); + } + let conn_clone = conn.clone(); + tokio::spawn(async move { + conn_clone.connect_internal(0).await; + }); + conn +}); + + } + pub fn connect(self: Arc) { + if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { + let cloned_self = self.clone(); + tokio::spawn(async move { + cloned_self.connect_internal(0).await; + }); + } + let cloned_self = self.clone(); + tokio::spawn(async move { + cloned_self.connect_internal(0).await; + }); + } + async fn connect_internal(self: Arc, mut retry: usize) { + loop { + })?; + + let server_pub_key_obj = load_public_key(server_pub_key).ok_or("Failed to load public key".to_string())?; + let server_pub_key_obj = load_public_key(server_pub_key).unwrap(); + + let decrypted_challenge = decrypt( + get_private_key(), + server_pub_key_obj, + let decrypted_challenge = decrypt_b64( + &secret_key_to_base64(&get_private_key()), + server_pub_key, + challenge, + ) + .map_err(|e| { + } + + if let Some(accepted) = final_cv.get_data(DataTypes::accepted).and_then(|v| v.as_bool()) { + if !accepted { + log_err!(PrintType::Omega, "Omega did not accept identification."); + return false; + } + } else { + log_err!(PrintType::Omega, "Omega response did not contain 'accepted' field."); + return false; + } + + tokio::spawn(async move { + let mut connected_iota_ids: Vec = Vec::new(); + let mut connected_user_ids: Vec = Vec::new(); + } + let msg_id = cv.get_id(); + log_in!(PrintType::Omikron, "{}", &cv.to_json().to_string()); + log_in!(PrintType::Omega, "{}", &cv.to_json().to_string()); + // Handle waiting tasks + if let Some(task) = WAITING_TASKS.remove(&msg_id) { + if (task.1)(self.clone(), cv.clone()) { + // continue in the read_loop + continue; + } + } else { + // Handle generic task + let generic_task_option = GENERIC_TASK.lock().await; + if let Some(generic_task) = generic_task_option.as_ref() { + if generic_task(self.clone(), cv.clone()) { + // continue in the read_loop + } + } + } + } + #[allow(non_snake_case)] + Some(Ok(Message::Close(_))) | None => break, + Some(Ok(Message::Close(_))) | None => continue, + Some(Err(_)) => break, + _ => {} + } +use async_tungstenite::tungstenite::Message; +use async_tungstenite::{WebSocketReceiver, WebSocketSender}; +use json::JsonValue; +use json::number::Number; +use rand::Rng; +use rand::distributions::Alphanumeric; +use std::sync::{Arc, Weak}; +use std::time::Duration; +use tokio::sync::RwLock; +use tokio_util::compat::Compat; +use tungstenite::Utf8Bytes; +use crate::calls::call_manager; +use crate::omega::omega_connection::{WAITING_TASKS, get_omega_connection}; +use crate::util::crypto_helper::{load_public_key, public_key_to_base64}; +use crate::util::crypto_util::{DataFormat, SecurePayload}; +use crate::util::logger::PrintType; +use crate::{ + // calls::call_manager::CallManager, + omega::omega_connection::OmegaConnection, +}; +use crate::{log_in, log_out}; +use crate::{get_private_key, get_public_key, log_in, log_out}; + +/// ClientConnection represents a WebSocket connection from a client device +pub struct ClientConnection { + /// WebSocket session + pub sender: Arc>>>, + pub receiver: Arc>>>, + /// User ID associated with this client + pub user_id: Arc>, + /// Whether this connection has been identified/authenticated + pub identified: Arc>, + /// Ping latency tracking + identified: Arc>, + challenged: Arc>, + challenge: Arc>, + pub ping: Arc>, + /// Weak reference to RhoConnection to avoid circular references + pub rho_connection: Arc>>>, + /// List of user IDs this client is interested in receiving updates about + pub_key: Arc>>>, + pub rho_connection: Arc>>>, + pub interested_users: Arc>>, +} + + user_id: Arc::new(RwLock::new(0)), + identified: Arc::new(RwLock::new(false)), + challenged: Arc::new(RwLock::new(false)), + challenge: Arc::new(RwLock::new(String::new())), + ping: Arc::new(RwLock::new(-1)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: Arc::new(RwLock::new(None)), + interested_users: Arc::new(RwLock::new(Vec::new())), + }) + } + + /// 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 + } + self.rho_connection.read().await.clone() + } + + /// Send a string message to the client + tokio::spawn(async move { + let cv = CommunicationValue::from_json(&message); + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_in!(PrintType::Client, "{}", &cv.to_json().to_string()); + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + // Handle identification + if cv.is_type(CommunicationType::identification) && !self.is_identified().await { + self.handle_identification(Arc::clone(&self), cv).await; + if !identified && cv.is_type(CommunicationType::identification) { + let user_id = cv + .get_data(DataTypes::user_id) + .and_then(|v| v.as_i64()) + .unwrap_or(0); + if user_id == 0 { + log_out!(PrintType::Client, "Invalid USER ID"); + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + self.close().await; + return; + } + + *self.user_id.write().await = user_id; + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, JsonValue::from(user_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + if let Ok(response_cv) = response_cv { + if !response_cv.is_type(CommunicationType::get_user_data) { + self.send_error_response(&cv.get_id(), CommunicationType::error_internal) + .await; + self.close().await; + return; + } + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .and_then(|v| v.as_str()) + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_public_key, + ) + .await; + self.close().await; + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + + let encrypted_challenge = + SecurePayload::new(challenge, DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + *self.identified.write().await = true; + + let challenge_msg = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data_str( + DataTypes::public_key, + public_key_to_base64(&get_public_key()), + ) + .add_data_str(DataTypes::challenge, encrypted_challenge); + + self.send_message(&challenge_msg).await; + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error_internal) + .await; + self.close().await; + return; + } + + return; + } + + if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { + let client_response = cv + .get_data(DataTypes::challenge) + .and_then(|v| v.as_str()) + .unwrap_or(""); + + if client_response == *self.challenge.read().await { + *self.challenged.write().await = true; + + let user_id = self.get_user_id().await; + + 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_no_iota, + ) + .await; + return; + } + }; + + // Set identification data + { + let mut user_id_guard = self.user_id.write().await; + *user_id_guard = user_id; + } + { + let mut identified_guard = self.identified.write().await; + *identified_guard = true; + } + *self.rho_connection.write().await = Some(Arc::clone(&rho_connection)); + + let response = + CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()); + self.send_message(&response).await; + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + self.close().await; + return; + } + return; + } + + if !self.is_identified().await { + self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) + .await; + self.close().await; + return; + } + + } + + /// Handle identification message + async fn handle_identification(&self, sarc: Arc, cv: CommunicationValue) { + // Extract user ID + let user_id: i64 = match cv.get_data(DataTypes::user_id) { + Some(id_str) => id_str.as_i64().unwrap_or(0), + None => { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id) + .await; + return; + } + }; + + // Validate private key + if let Some(private_key_hash) = cv.get_data(DataTypes::private_key_hash) { + println!("private_key_hash: {}", private_key_hash); + let is_valid = true; // NO VALIDATION, + // SWAP TO AUTH VIA CHALLENGE + // auth_connector::is_private_key_valid(user_id, &private_key_hash.to_string()).await; + + if !is_valid { + println!("Invalid private key"); + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_private_key, + ) + .await; + return; + } + } else { + log_in!(PrintType::Client, "Missing private key"); + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_private_key) + .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_no_iota) + .await; + return; + } + }; + + // Set identification data + { + let mut user_id_guard = self.user_id.write().await; + *user_id_guard = user_id; + } + { + let mut identified_guard = self.identified.write().await; + *identified_guard = true; + } + + self.set_rho_connection(Arc::downgrade(&rho_connection)) + .await; + + rho_connection.add_client_connection(Arc::from(sarc)).await; + + 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, cv: CommunicationValue) { + // Update our ping if provided + user_id: Arc::clone(&self.user_id), + identified: Arc::clone(&self.identified), + challenged: Arc::clone(&self.challenged), + challenge: Arc::clone(&self.challenge), + ping: Arc::clone(&self.ping), + pub_key: Arc::clone(&self.pub_key), + rho_connection: Arc::clone(&self.rho_connection), + interested_users: Arc::clone(&self.interested_users), + } +use crate::util::crypto_helper::load_public_key; +use crate::util::crypto_helper::public_key_to_base64; +use crate::util::crypto_util::DataFormat; +use crate::util::crypto_util::SecurePayload; +use crate::util::logger::PrintType; +use async_tungstenite::WebSocketReceiver; +use async_tungstenite::WebSocketSender; +}; +use tokio::sync::RwLock; +use tokio::sync::mpsc; +use tokio_util::compat::Compat; +use tungstenite::Utf8Bytes; +use uuid::Uuid; +use warp::filters::method::get; +use x448::PublicKey; + +use super::{rho_connection::RhoConnection, rho_manager}; + } + + log_in!(PrintType::Iota, "{}", cv.to_json().to_string()); + + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + .unwrap_or(0); + if iota_id == 0 { + log_out!(PrintType::Iota, "Invalid IOTA ID"); + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + .await; + self.close().await; + } + + let user_ids_json = cv + .get_data(DataTypes::user_ids) + .unwrap_or(&JsonValue::Null) + .clone(); + let mut user_ids = Vec::new(); + if let JsonValue::Array(ids) = user_ids_json { + for id_val in ids { + if let Some(id) = id_val.as_i64() { + user_ids.push(id); + } + } + } + + *self.iota_id.write().await = iota_id; + *self.user_ids.write().await = user_ids; + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(cv.get_id()) + + let encrypted_challenge = + encrypt(get_private_key(), pub_key, &challenge).unwrap_or_default(); + SecurePayload::new(&challenge, DataFormat::Base64, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + *self.identified.write().await = true; + + + let iota_id = self.get_iota_id().await; + let user_ids = self.get_user_ids().await; + + let mut validated_user_ids: Vec = Vec::new(); + for user_id in user_ids { + validated_user_ids.push(user_id); + } + + if rho_manager::contains_iota(iota_id).await { + if let Some(existing_rho) = rho_manager::get_rho_by_iota(iota_id).await { + } + + // Inform Omega & Verify Users + let iota_users_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::iota_connected).add_data( + DataTypes::iota_id, + JsonValue::from(self.get_iota_id().await), + ), + Some(Duration::from_secs(20)), + ) + .await; + + let mut user_ids: Vec = Vec::new(); + if let Ok(iota_users_cv) = iota_users_cv { + if !iota_users_cv.is_type(CommunicationType::iota_user_data) { + log_err!( + PrintType::Omikron, + "Invalid communication type {:?}", + iota_users_cv.get_type() + ); + return; + } + let val_user_ids = iota_users_cv.get_data(DataTypes::user_ids).unwrap().clone(); + + match val_user_ids { + JsonValue::Array(arr) => { + for item in arr { + if let JsonValue::Number(_) = item { + user_ids.push(item.as_i64().unwrap_or(0)); + } + } + } + _ => {} + } + } else { + log_err!(PrintType::Omikron, "Failed to retrieve user IDs"); + } + log_in!(PrintType::General, "User IDs: {:?}", user_ids.clone()); + + *self.user_ids.write().await = user_ids.clone(); + let rho_connection = + Arc::new(RhoConnection::new(self.clone(), validated_user_ids.clone()).await); + Arc::new(RhoConnection::new(self.clone(), user_ids.clone()).await); + + self.set_rho_connection(Arc::downgrade(&rho_connection)) + .await; + + let mut str = String::new(); + for id in &validated_user_ids { + for id in &user_ids { + str.push_str(&format!(",{}", id)); + } + if !str.is_empty() { + .with_id(cv.get_id()) + .add_data_str(DataTypes::accepted_ids, str) + .add_data_str(DataTypes::accepted, validated_user_ids.len().to_string()), + .add_data_str(DataTypes::accepted, user_ids.len().to_string()), + ) + .await; + } else { + } + } + pub async fn await_response( + &self, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + let task_tx = tx.clone(); + self.waiting_tasks.insert( + msg_id, + Box::new(move |_, response_cv| { + let inner_tx = task_tx.clone(); + tokio::spawn(async move { + if let Err(e) = inner_tx.send(response_cv).await { + log_err!( + PrintType::Iota, + "Failed to send response back to awaiter: {}", + e + ); + } + }); + true + }), + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(None) => Err("Failed to receive response, channel was closed.".to_string()), + Err(_) => { + self.waiting_tasks.remove(&msg_id); + Err(format!( + "Request timed out after {} seconds.", + timeout.as_secs() + )) + } + } + } +} + +impl std::fmt::Debug for IotaConnection { + }; + + // Notify OmegaConnection about the new Iota + OmegaConnection::connect_iota(rho_connection.get_iota_id().await, user_ids).await; + + rho_connection + } + + let connections = self.client_connections.read().await; + for connection in connections.iter() { + if connection.get_user_id().await == cv.receiver { + if connection.get_user_id().await == cv.get_receiver() { + connection.send_message(&cv).await; + } + } +pub mod config_util; +pub mod crypto_helper; +pub mod crypto_util; +pub mod file_util; +pub mod logger; + +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, Payload}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; +use hkdf::Hkdf; +use sha2::{Digest, Sha256}; +use std::fmt; +use x448::{PublicKey, Secret}; + +// --- Custom Errors --- +#[derive(Debug)] +pub enum SecurePayloadError { + InvalidBase64, + InvalidHex, + EncryptionError, + DecryptionError, + InvalidKeyLength, +} + +// --- Data Format Enum --- +#[derive(Clone, Copy, Debug)] +pub enum DataFormat { + Raw, + Base64, + Hex, +} + +// --- Main Class Structure --- +pub struct SecurePayload { + /// The internal canonical representation is always raw bytes. + inner_data: Vec, + /// The private key of the user associated with this payload instance. + private_key: Secret, +} + +impl Clone for SecurePayload { + fn clone(&self) -> Self { + Self { + inner_data: self.inner_data.clone(), + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + } + } +} + +impl SecurePayload { + /// Clear Constructor: Takes data in any format and the user's private key. + pub fn new>( + data: T, + format: DataFormat, + private_key: S, + ) -> Result + where + S: Into, + { + let raw_data = match format { + DataFormat::Raw => data.as_ref().to_vec(), + DataFormat::Base64 => BASE64_STD + .decode(data.as_ref()) + .map_err(|_| SecurePayloadError::InvalidBase64)?, + DataFormat::Hex => { + hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? + } + }; + + Ok(Self { + inner_data: raw_data, + private_key: private_key.into(), + }) + } + + /// Helper to get the public key associated with this instance's private key. + pub fn get_public_key(&self) -> [u8; 56] { + *PublicKey::from(&self.private_key).as_bytes() + } + + /// Exports the internal data to the requested format + pub fn export(&self, format: DataFormat) -> String { + match format.into() { + DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), + DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), + DataFormat::Hex => hex::encode(&self.inner_data), + } + } + + /// Access raw bytes directly + pub fn get_bytes(&self) -> &[u8] { + &self.inner_data + } + + /// Returns the SHA-256 Hash of the data in the requested format + pub fn get_hash(&self, format: DataFormat) -> String { + let mut hasher = Sha256::new(); + hasher.update(&self.inner_data); + let result = hasher.finalize(); + + match format { + DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), + DataFormat::Base64 => BASE64_STD.encode(result), + DataFormat::Hex => hex::encode(result), + } + } + + /// Encrypts the held data for a specific recipient using AES-256-GCM. + /// The message will contain ONLY the ciphertext. + pub fn encrypt_x448(&self, public_key: S) -> Result + where + S: Into, + { + let peer_pub = public_key.into(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + println!( + "Encryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 3. Key & Nonce Derivation (HKDF) + // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 4. Encrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let ciphertext = cipher + .encrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + // 5. Result is ONLY the ciphertext. No key or nonce is packed. + Ok(SecurePayload { + inner_data: ciphertext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } + + /// Decrypts the held data providing the sender's public key manually. + pub fn decrypt_to_format( + &self, + peer_public_key_bytes: &[u8; 56], + output_format: DataFormat, + ) -> Result { + let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; + Ok(decrypted_instance.export(output_format)) + } + + /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. + pub fn decrypt_x448( + &self, + peer_public_key_bytes: &[u8; 56], + ) -> Result { + // 1. Perform Exchange + let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + // LOGGING: Shared Secret + println!( + "Decryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 2. Key & Nonce Derivation (Must match encryption exactly) + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 3. Decrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let plaintext = cipher + .decrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + Ok(SecurePayload { + inner_data: plaintext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } +} From c17d929e0d4270d23608e5762dfb5ca732bc7322 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 00:08:11 +0100 Subject: [PATCH 027/220] adding conversations, better loading of enums --- Cargo.lock | 21 + Cargo.toml | 3 + src/data/communication.rs | 201 ++-------- src/util/crypto_util.rs | 802 -------------------------------------- src/util/mod.rs | 1 + 5 files changed, 51 insertions(+), 977 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05b9215..972464a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "dotenv", "futures", "futures-util", + "hex", "hkdf", "http-body-util", "hyper", @@ -33,6 +34,8 @@ dependencies = [ "sha1", "sha2", "sqlx", + "strum", + "strum_macros", "sysinfo", "tokio", "tokio-rustls", @@ -3139,6 +3142,24 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index d5e2dcb..344dbc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ dashmap = "6.1.0" dotenv = "0.15.0" futures = "0.3.31" futures-util = "0.3.31" +hex = "0.4.3" hkdf = "0.12.4" http-body-util = "0.1.3" hyper = { version = "1.8.1", features = ["full"] } @@ -31,6 +32,8 @@ rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } +strum = "0.27.2" +strum_macros = "0.27.2" sysinfo = "0.37.2" tokio = { version = "*", features = ["full"] } tokio-rustls = "0.26.4" diff --git a/src/data/communication.rs b/src/data/communication.rs index b2b5898..fd57fe1 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -2,9 +2,11 @@ use json::number::Number; use json::{Array, JsonValue, object, parse}; use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; use uuid::Uuid; -#[derive(Eq, Hash, PartialEq, Clone, Debug)] +#[derive(Eq, Hash, PartialEq, EnumIter, Clone, Debug)] #[allow(non_camel_case_types, dead_code)] pub enum DataTypes { error_type, @@ -14,6 +16,7 @@ pub enum DataTypes { settings, settings_name, chat_partner_id, + chat_partner_name, iota_id, user_id, user_ids, @@ -88,91 +91,21 @@ pub enum DataTypes { impl DataTypes { pub fn parse(p0: String) -> DataTypes { - let normalized = p0.to_lowercase().replace('_', ""); - - match normalized.as_str() { - "errortype" => DataTypes::error_type, - "chatpartnerid" => DataTypes::chat_partner_id, - "registerid" => DataTypes::register_id, - "uuid" => DataTypes::uuid, - "settings" => DataTypes::settings, - "settingsname" => DataTypes::settings_name, - "iotaid" => DataTypes::iota_id, - "userid" => DataTypes::user_id, - "userids" => DataTypes::user_ids, - "iotaids" => DataTypes::iota_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, - "calltoken" => DataTypes::call_token, - "untill" => DataTypes::untill, - "enable" => DataTypes::enable, - "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, - "rhoconnections" => DataTypes::rho_connections, - "user" => DataTypes::user, - "onlinestatus" => DataTypes::online_status, - "omikronid" => DataTypes::omikron_id, - "omikronconnections" => DataTypes::omikron_connections, - "resettoken" => DataTypes::reset_token, - "newtoken" => DataTypes::new_token, - _ => DataTypes::error_type, // fallback if unknown + for datatype in DataTypes::iter() { + if datatype.to_string().to_lowercase().replace('_', "") + == p0.to_lowercase().replace('_', "") + { + return datatype; + } } + DataTypes::error_type + } + pub fn to_string(&self) -> String { + return format!("{:?}", self); } } -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Clone, EnumIter, Debug)] #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, @@ -218,7 +151,7 @@ pub enum CommunicationType { register_iota_success, ping, pong, - add_chat, + add_conversation, send_chat, client_changed, client_connected, @@ -266,99 +199,17 @@ pub enum CommunicationType { } impl CommunicationType { pub fn parse(p0: String) -> CommunicationType { - let normalized = p0.to_lowercase().replace('_', ""); - - match normalized.as_str() { - "watchstream" => CommunicationType::watch_stream, - "calltoken" => CommunicationType::call_token, - "callinvite" => CommunicationType::call_invite, - "calldisconnectuser" => CommunicationType::call_disconnect_user, - "calltimeoutuser" => CommunicationType::call_timeout_user, - "callsetanonymousjoining" => CommunicationType::call_set_anonymous_joining, - "endcall" => CommunicationType::end_call, - "function" => CommunicationType::function, - "update" => CommunicationType::update, - "createuser" => CommunicationType::create_user, - "errorinternal" => CommunicationType::error_internal, - "errorinvaliddata" => CommunicationType::error_invalid_data, - "errorinvaliduserid" => CommunicationType::error_invalid_user_id, - "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, - "errornotfound" => CommunicationType::error_not_found, - "errornotauthenticated" => CommunicationType::error_not_authenticated, - "errornoiota" => CommunicationType::error_no_iota, - "errorinvalidchallenge" => CommunicationType::error_invalid_challenge, - "errorinvalidpublickey" => CommunicationType::error_invalid_public_key, - "errorinvalidsecret" => CommunicationType::error_invalid_secret, - "errorinvalidprivatekey" => CommunicationType::error_invalid_private_key, - "errornouserid" => CommunicationType::error_no_user_id, - "errornocallid" => CommunicationType::error_no_call_id, - "errorinvalidcallid" => CommunicationType::error_invalid_call_id, - "success" => CommunicationType::success, - "settingssave" => CommunicationType::settings_save, - "settingsload" => CommunicationType::settings_load, - "settingslist" => CommunicationType::settings_list, - "message" => CommunicationType::message, - "messagesend" => CommunicationType::message_send, - "messagelive" => CommunicationType::message_live, - "messageotheriota" => CommunicationType::message_other_iota, - "messagechunk" => CommunicationType::message_chunk, - "messagesget" => CommunicationType::messages_get, - "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, - "registeriota" => CommunicationType::register_iota, - "registeriotasuccess" => CommunicationType::register_iota_success, - "ping" => CommunicationType::ping, - "pong" => CommunicationType::pong, - "addchat" => CommunicationType::add_chat, - "sendchat" => CommunicationType::send_chat, - "clientchanged" => CommunicationType::client_changed, - "clientconnected" => CommunicationType::client_connected, - "clientdisconnected" => CommunicationType::client_disconnected, - "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, - "rhoupdate" => CommunicationType::rho_update, - - "iotaconnected" => CommunicationType::iota_connected, - "iotadisconnected" => CommunicationType::iota_disconnected, - "userconnected" => CommunicationType::user_connected, - "userdisconnected" => CommunicationType::user_disconnected, - "syncclientiotastatus" => CommunicationType::sync_client_iota_status, - - "getuserdata" => CommunicationType::get_user_data, - "getiotadata" => CommunicationType::get_iota_data, - "iotauserdata" => CommunicationType::iota_user_data, - - "changeuserdata" => CommunicationType::change_user_data, - "changeiotadata" => CommunicationType::change_iota_data, - - "getregister" => CommunicationType::get_register, - "completeregisteruser" => CommunicationType::complete_register_user, - "completeregisteriota" => CommunicationType::complete_register_iota, - "deleteuser" => CommunicationType::delete_user, - "deleteiota" => CommunicationType::delete_iota, - - "startregister" => CommunicationType::start_register, - "completeregister" => CommunicationType::complete_register, - - _ => CommunicationType::error, + for datatype in CommunicationType::iter() { + if datatype.to_string().to_lowercase().replace('_', "") + == p0.to_lowercase().replace('_', "") + { + return datatype; + } } + CommunicationType::error + } + pub fn to_string(&self) -> String { + return format!("{:?}", self); } } diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs index 8b6768f..066149a 100644 --- a/src/util/crypto_util.rs +++ b/src/util/crypto_util.rs @@ -1,805 +1,3 @@ -version = "0.1.0" -dependencies = [ - "aes", - "aes-gcm", - "ansi_term", - "async-tungstenite", - "axum", - "base64 0.22.1", - "block-modes", - "bytes", - "cbc", - "chacha20poly1305", - "chrono", - "cmake", - "crossterm", - "futures-util", - "hex", - "hkdf", - "http 1.4.0", - "hyper", - "json", - -[[package]] -name = "block-modes" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2211b0817f061502a8dd9f11a37e879e79763e3c698d2418cf824d8cb2f21e" - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - -[[package]] -name = "cc" -version = "1.2.52" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" - "crypto-common", - "inout", - "zeroize", -] - -[[package]] - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding", - "generic-array", -] - - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -aes-gcm = "0.10.3" -tokio-native-tls = "0.3.1" -hkdf = "0.12.4" -chacha20poly1305 = "0.10.1" -block-modes = "0.9.1" -cbc = "0.1.2" -aes = "0.8.4" - - get_user_data, - get_iota_data, - iota_user_data, - - change_user_data, - change_iota_data, - "getuserdata" => CommunicationType::get_user_data, - "getiotadata" => CommunicationType::get_iota_data, - "iotauserdata" => CommunicationType::iota_user_data, - - "changeuserdata" => CommunicationType::change_user_data, - "changeiotadata" => CommunicationType::change_iota_data, -#[derive(Debug, Clone)] -pub struct CommunicationValue { - pub id: Uuid, - pub comm_type: CommunicationType, - pub sender: i64, - pub receiver: i64, - pub data: HashMap, - id: Uuid, - comm_type: CommunicationType, - sender: i64, - receiver: i64, - data: HashMap, -} - -#[allow(dead_code)] - } - - pub(crate) fn is_type(&self, p0: CommunicationType) -> bool { - pub fn get_type(&self) -> CommunicationType { - self.comm_type.clone() - } - pub fn is_type(&self, p0: CommunicationType) -> bool { - self.comm_type == p0 - } - pub fn to_json(&self) -> JsonValue { -use json::{JsonValue, number::Number}; -use once_cell::sync::Lazy; -use std::{ - collections::HashMap, - env, - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, - time::Duration, -}; -use std::{collections::HashMap, env, sync::Arc, time::Duration}; -use tokio::{ - net::TcpStream, - sync::{Mutex, RwLock, mpsc}, - get_private_key, log, log_in, log_out, - rho::rho_manager::{self, RHO_CONNECTIONS}, - util::crypto_helper::{decrypt, load_public_key}, - util::crypto_helper::{decrypt_b64, secret_key_to_base64}, - util::logger::PrintType, -}; -use crate::{log_err, util::crypto_helper::secret_key_to_base64}; -use crate::{log_err, util::crypto_helper::load_public_key}; - -pub static WAITING_TASKS: Lazy< - DashMap, CommunicationValue) -> bool + Send + Sync>>, -> = Lazy::new(DashMap::new); - -/// Flag to ensure the connection loop is only started once. -static CONNECTION_LOOP_STARTED: AtomicBool = AtomicBool::new(false); - -static GENERIC_TASK: Lazy< - Mutex, CommunicationValue) -> bool + Send + Sync>>>, -> = Lazy::new(|| Mutex::new(None)); - -static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { - let conn = Arc::new(OmegaConnection::new()); - if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { - let conn_clone = conn.clone(); - tokio::spawn(async move { - conn_clone.connect_internal(0).await; - }); - } - let conn_clone = conn.clone(); - tokio::spawn(async move { - conn_clone.connect_internal(0).await; - }); - conn -}); - - } - pub fn connect(self: Arc) { - if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { - let cloned_self = self.clone(); - tokio::spawn(async move { - cloned_self.connect_internal(0).await; - }); - } - let cloned_self = self.clone(); - tokio::spawn(async move { - cloned_self.connect_internal(0).await; - }); - } - async fn connect_internal(self: Arc, mut retry: usize) { - loop { - })?; - - let server_pub_key_obj = load_public_key(server_pub_key).ok_or("Failed to load public key".to_string())?; - let server_pub_key_obj = load_public_key(server_pub_key).unwrap(); - - let decrypted_challenge = decrypt( - get_private_key(), - server_pub_key_obj, - let decrypted_challenge = decrypt_b64( - &secret_key_to_base64(&get_private_key()), - server_pub_key, - challenge, - ) - .map_err(|e| { - } - - if let Some(accepted) = final_cv.get_data(DataTypes::accepted).and_then(|v| v.as_bool()) { - if !accepted { - log_err!(PrintType::Omega, "Omega did not accept identification."); - return false; - } - } else { - log_err!(PrintType::Omega, "Omega response did not contain 'accepted' field."); - return false; - } - - tokio::spawn(async move { - let mut connected_iota_ids: Vec = Vec::new(); - let mut connected_user_ids: Vec = Vec::new(); - } - let msg_id = cv.get_id(); - log_in!(PrintType::Omikron, "{}", &cv.to_json().to_string()); - log_in!(PrintType::Omega, "{}", &cv.to_json().to_string()); - // Handle waiting tasks - if let Some(task) = WAITING_TASKS.remove(&msg_id) { - if (task.1)(self.clone(), cv.clone()) { - // continue in the read_loop - continue; - } - } else { - // Handle generic task - let generic_task_option = GENERIC_TASK.lock().await; - if let Some(generic_task) = generic_task_option.as_ref() { - if generic_task(self.clone(), cv.clone()) { - // continue in the read_loop - } - } - } - } - #[allow(non_snake_case)] - Some(Ok(Message::Close(_))) | None => break, - Some(Ok(Message::Close(_))) | None => continue, - Some(Err(_)) => break, - _ => {} - } -use async_tungstenite::tungstenite::Message; -use async_tungstenite::{WebSocketReceiver, WebSocketSender}; -use json::JsonValue; -use json::number::Number; -use rand::Rng; -use rand::distributions::Alphanumeric; -use std::sync::{Arc, Weak}; -use std::time::Duration; -use tokio::sync::RwLock; -use tokio_util::compat::Compat; -use tungstenite::Utf8Bytes; -use crate::calls::call_manager; -use crate::omega::omega_connection::{WAITING_TASKS, get_omega_connection}; -use crate::util::crypto_helper::{load_public_key, public_key_to_base64}; -use crate::util::crypto_util::{DataFormat, SecurePayload}; -use crate::util::logger::PrintType; -use crate::{ - // calls::call_manager::CallManager, - omega::omega_connection::OmegaConnection, -}; -use crate::{log_in, log_out}; -use crate::{get_private_key, get_public_key, log_in, log_out}; - -/// ClientConnection represents a WebSocket connection from a client device -pub struct ClientConnection { - /// WebSocket session - pub sender: Arc>>>, - pub receiver: Arc>>>, - /// User ID associated with this client - pub user_id: Arc>, - /// Whether this connection has been identified/authenticated - pub identified: Arc>, - /// Ping latency tracking - identified: Arc>, - challenged: Arc>, - challenge: Arc>, - pub ping: Arc>, - /// Weak reference to RhoConnection to avoid circular references - pub rho_connection: Arc>>>, - /// List of user IDs this client is interested in receiving updates about - pub_key: Arc>>>, - pub rho_connection: Arc>>>, - pub interested_users: Arc>>, -} - - user_id: Arc::new(RwLock::new(0)), - identified: Arc::new(RwLock::new(false)), - challenged: Arc::new(RwLock::new(false)), - challenge: Arc::new(RwLock::new(String::new())), - ping: Arc::new(RwLock::new(-1)), - pub_key: Arc::new(RwLock::new(None)), - rho_connection: Arc::new(RwLock::new(None)), - interested_users: Arc::new(RwLock::new(Vec::new())), - }) - } - - /// 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 - } - self.rho_connection.read().await.clone() - } - - /// Send a string message to the client - tokio::spawn(async move { - let cv = CommunicationValue::from_json(&message); - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; - } - log_in!(PrintType::Client, "{}", &cv.to_json().to_string()); - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; - - // Handle identification - if cv.is_type(CommunicationType::identification) && !self.is_identified().await { - self.handle_identification(Arc::clone(&self), cv).await; - if !identified && cv.is_type(CommunicationType::identification) { - let user_id = cv - .get_data(DataTypes::user_id) - .and_then(|v| v.as_i64()) - .unwrap_or(0); - if user_id == 0 { - log_out!(PrintType::Client, "Invalid USER ID"); - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; - self.close().await; - return; - } - - *self.user_id.write().await = user_id; - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, JsonValue::from(user_id)); - - let response_cv = get_omega_connection() - .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) - .await; - - if let Ok(response_cv) = response_cv { - if !response_cv.is_type(CommunicationType::get_user_data) { - self.send_error_response(&cv.get_id(), CommunicationType::error_internal) - .await; - self.close().await; - return; - } - - let base64_pub = response_cv - .get_data(DataTypes::public_key) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - let pub_key = match load_public_key(base64_pub) { - Some(pk) => pk, - None => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_public_key, - ) - .await; - self.close().await; - return; - } - }; - - *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge.clone(); - - let encrypted_challenge = - SecurePayload::new(challenge, DataFormat::Raw, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - *self.identified.write().await = true; - - let challenge_msg = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data_str( - DataTypes::public_key, - public_key_to_base64(&get_public_key()), - ) - .add_data_str(DataTypes::challenge, encrypted_challenge); - - self.send_message(&challenge_msg).await; - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_internal) - .await; - self.close().await; - return; - } - - return; - } - - if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { - let client_response = cv - .get_data(DataTypes::challenge) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - if client_response == *self.challenge.read().await { - *self.challenged.write().await = true; - - let user_id = self.get_user_id().await; - - 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_no_iota, - ) - .await; - return; - } - }; - - // Set identification data - { - let mut user_id_guard = self.user_id.write().await; - *user_id_guard = user_id; - } - { - let mut identified_guard = self.identified.write().await; - *identified_guard = true; - } - *self.rho_connection.write().await = Some(Arc::clone(&rho_connection)); - - let response = - CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_not_authenticated, - ) - .await; - self.close().await; - return; - } - return; - } - - if !self.is_identified().await { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) - .await; - self.close().await; - return; - } - - } - - /// Handle identification message - async fn handle_identification(&self, sarc: Arc, cv: CommunicationValue) { - // Extract user ID - let user_id: i64 = match cv.get_data(DataTypes::user_id) { - Some(id_str) => id_str.as_i64().unwrap_or(0), - None => { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id) - .await; - return; - } - }; - - // Validate private key - if let Some(private_key_hash) = cv.get_data(DataTypes::private_key_hash) { - println!("private_key_hash: {}", private_key_hash); - let is_valid = true; // NO VALIDATION, - // SWAP TO AUTH VIA CHALLENGE - // auth_connector::is_private_key_valid(user_id, &private_key_hash.to_string()).await; - - if !is_valid { - println!("Invalid private key"); - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_private_key, - ) - .await; - return; - } - } else { - log_in!(PrintType::Client, "Missing private key"); - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_private_key) - .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_no_iota) - .await; - return; - } - }; - - // Set identification data - { - let mut user_id_guard = self.user_id.write().await; - *user_id_guard = user_id; - } - { - let mut identified_guard = self.identified.write().await; - *identified_guard = true; - } - - self.set_rho_connection(Arc::downgrade(&rho_connection)) - .await; - - rho_connection.add_client_connection(Arc::from(sarc)).await; - - 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, cv: CommunicationValue) { - // Update our ping if provided - user_id: Arc::clone(&self.user_id), - identified: Arc::clone(&self.identified), - challenged: Arc::clone(&self.challenged), - challenge: Arc::clone(&self.challenge), - ping: Arc::clone(&self.ping), - pub_key: Arc::clone(&self.pub_key), - rho_connection: Arc::clone(&self.rho_connection), - interested_users: Arc::clone(&self.interested_users), - } -use crate::util::crypto_helper::load_public_key; -use crate::util::crypto_helper::public_key_to_base64; -use crate::util::crypto_util::DataFormat; -use crate::util::crypto_util::SecurePayload; -use crate::util::logger::PrintType; -use async_tungstenite::WebSocketReceiver; -use async_tungstenite::WebSocketSender; -}; -use tokio::sync::RwLock; -use tokio::sync::mpsc; -use tokio_util::compat::Compat; -use tungstenite::Utf8Bytes; -use uuid::Uuid; -use warp::filters::method::get; -use x448::PublicKey; - -use super::{rho_connection::RhoConnection, rho_manager}; - } - - log_in!(PrintType::Iota, "{}", cv.to_json().to_string()); - - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; - - .unwrap_or(0); - if iota_id == 0 { - log_out!(PrintType::Iota, "Invalid IOTA ID"); - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; - self.close().await; - } - - let user_ids_json = cv - .get_data(DataTypes::user_ids) - .unwrap_or(&JsonValue::Null) - .clone(); - let mut user_ids = Vec::new(); - if let JsonValue::Array(ids) = user_ids_json { - for id_val in ids { - if let Some(id) = id_val.as_i64() { - user_ids.push(id); - } - } - } - - *self.iota_id.write().await = iota_id; - *self.user_ids.write().await = user_ids; - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - - let encrypted_challenge = - encrypt(get_private_key(), pub_key, &challenge).unwrap_or_default(); - SecurePayload::new(&challenge, DataFormat::Base64, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - *self.identified.write().await = true; - - - let iota_id = self.get_iota_id().await; - let user_ids = self.get_user_ids().await; - - let mut validated_user_ids: Vec = Vec::new(); - for user_id in user_ids { - validated_user_ids.push(user_id); - } - - if rho_manager::contains_iota(iota_id).await { - if let Some(existing_rho) = rho_manager::get_rho_by_iota(iota_id).await { - } - - // Inform Omega & Verify Users - let iota_users_cv = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::iota_connected).add_data( - DataTypes::iota_id, - JsonValue::from(self.get_iota_id().await), - ), - Some(Duration::from_secs(20)), - ) - .await; - - let mut user_ids: Vec = Vec::new(); - if let Ok(iota_users_cv) = iota_users_cv { - if !iota_users_cv.is_type(CommunicationType::iota_user_data) { - log_err!( - PrintType::Omikron, - "Invalid communication type {:?}", - iota_users_cv.get_type() - ); - return; - } - let val_user_ids = iota_users_cv.get_data(DataTypes::user_ids).unwrap().clone(); - - match val_user_ids { - JsonValue::Array(arr) => { - for item in arr { - if let JsonValue::Number(_) = item { - user_ids.push(item.as_i64().unwrap_or(0)); - } - } - } - _ => {} - } - } else { - log_err!(PrintType::Omikron, "Failed to retrieve user IDs"); - } - log_in!(PrintType::General, "User IDs: {:?}", user_ids.clone()); - - *self.user_ids.write().await = user_ids.clone(); - let rho_connection = - Arc::new(RhoConnection::new(self.clone(), validated_user_ids.clone()).await); - Arc::new(RhoConnection::new(self.clone(), user_ids.clone()).await); - - self.set_rho_connection(Arc::downgrade(&rho_connection)) - .await; - - let mut str = String::new(); - for id in &validated_user_ids { - for id in &user_ids { - str.push_str(&format!(",{}", id)); - } - if !str.is_empty() { - .with_id(cv.get_id()) - .add_data_str(DataTypes::accepted_ids, str) - .add_data_str(DataTypes::accepted, validated_user_ids.len().to_string()), - .add_data_str(DataTypes::accepted, user_ids.len().to_string()), - ) - .await; - } else { - } - } - pub async fn await_response( - &self, - cv: &CommunicationValue, - timeout_duration: Option, - ) -> Result { - let (tx, mut rx) = mpsc::channel(1); - let msg_id = cv.get_id(); - - let task_tx = tx.clone(); - self.waiting_tasks.insert( - msg_id, - Box::new(move |_, response_cv| { - let inner_tx = task_tx.clone(); - tokio::spawn(async move { - if let Err(e) = inner_tx.send(response_cv).await { - log_err!( - PrintType::Iota, - "Failed to send response back to awaiter: {}", - e - ); - } - }); - true - }), - ); - - self.send_message(cv).await; - - let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); - - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response_cv)) => Ok(response_cv), - Ok(None) => Err("Failed to receive response, channel was closed.".to_string()), - Err(_) => { - self.waiting_tasks.remove(&msg_id); - Err(format!( - "Request timed out after {} seconds.", - timeout.as_secs() - )) - } - } - } -} - -impl std::fmt::Debug for IotaConnection { - }; - - // Notify OmegaConnection about the new Iota - OmegaConnection::connect_iota(rho_connection.get_iota_id().await, user_ids).await; - - rho_connection - } - - let connections = self.client_connections.read().await; - for connection in connections.iter() { - if connection.get_user_id().await == cv.receiver { - if connection.get_user_id().await == cv.get_receiver() { - connection.send_message(&cv).await; - } - } -pub mod config_util; -pub mod crypto_helper; -pub mod crypto_util; -pub mod file_util; -pub mod logger; - use aes_gcm::{ Aes256Gcm, Nonce, aead::{Aead, KeyInit, Payload}, diff --git a/src/util/mod.rs b/src/util/mod.rs index 1cd66a6..1897223 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,4 @@ pub mod crypto_helper; +pub mod crypto_util; pub mod file_util; pub mod logger; From dddf53d3525f45a4946234e1686c25f278ed4fdc Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 00:08:11 +0100 Subject: [PATCH 028/220] adding conversations, better loading of enums --- Cargo.lock | 21 + Cargo.toml | 3 + src/data/communication.rs | 201 ++-------- src/util/crypto_util.rs | 802 -------------------------------------- src/util/mod.rs | 1 + 5 files changed, 51 insertions(+), 977 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 05b9215..972464a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "dotenv", "futures", "futures-util", + "hex", "hkdf", "http-body-util", "hyper", @@ -33,6 +34,8 @@ dependencies = [ "sha1", "sha2", "sqlx", + "strum", + "strum_macros", "sysinfo", "tokio", "tokio-rustls", @@ -3139,6 +3142,24 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" diff --git a/Cargo.toml b/Cargo.toml index d5e2dcb..344dbc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ dashmap = "6.1.0" dotenv = "0.15.0" futures = "0.3.31" futures-util = "0.3.31" +hex = "0.4.3" hkdf = "0.12.4" http-body-util = "0.1.3" hyper = { version = "1.8.1", features = ["full"] } @@ -31,6 +32,8 @@ rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } +strum = "0.27.2" +strum_macros = "0.27.2" sysinfo = "0.37.2" tokio = { version = "*", features = ["full"] } tokio-rustls = "0.26.4" diff --git a/src/data/communication.rs b/src/data/communication.rs index b2b5898..fd57fe1 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -2,9 +2,11 @@ use json::number::Number; use json::{Array, JsonValue, object, parse}; use std::collections::HashMap; use std::time::{SystemTime, UNIX_EPOCH}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; use uuid::Uuid; -#[derive(Eq, Hash, PartialEq, Clone, Debug)] +#[derive(Eq, Hash, PartialEq, EnumIter, Clone, Debug)] #[allow(non_camel_case_types, dead_code)] pub enum DataTypes { error_type, @@ -14,6 +16,7 @@ pub enum DataTypes { settings, settings_name, chat_partner_id, + chat_partner_name, iota_id, user_id, user_ids, @@ -88,91 +91,21 @@ pub enum DataTypes { impl DataTypes { pub fn parse(p0: String) -> DataTypes { - let normalized = p0.to_lowercase().replace('_', ""); - - match normalized.as_str() { - "errortype" => DataTypes::error_type, - "chatpartnerid" => DataTypes::chat_partner_id, - "registerid" => DataTypes::register_id, - "uuid" => DataTypes::uuid, - "settings" => DataTypes::settings, - "settingsname" => DataTypes::settings_name, - "iotaid" => DataTypes::iota_id, - "userid" => DataTypes::user_id, - "userids" => DataTypes::user_ids, - "iotaids" => DataTypes::iota_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, - "calltoken" => DataTypes::call_token, - "untill" => DataTypes::untill, - "enable" => DataTypes::enable, - "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, - "rhoconnections" => DataTypes::rho_connections, - "user" => DataTypes::user, - "onlinestatus" => DataTypes::online_status, - "omikronid" => DataTypes::omikron_id, - "omikronconnections" => DataTypes::omikron_connections, - "resettoken" => DataTypes::reset_token, - "newtoken" => DataTypes::new_token, - _ => DataTypes::error_type, // fallback if unknown + for datatype in DataTypes::iter() { + if datatype.to_string().to_lowercase().replace('_', "") + == p0.to_lowercase().replace('_', "") + { + return datatype; + } } + DataTypes::error_type + } + pub fn to_string(&self) -> String { + return format!("{:?}", self); } } -#[derive(PartialEq, Clone, Debug)] +#[derive(PartialEq, Clone, EnumIter, Debug)] #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, @@ -218,7 +151,7 @@ pub enum CommunicationType { register_iota_success, ping, pong, - add_chat, + add_conversation, send_chat, client_changed, client_connected, @@ -266,99 +199,17 @@ pub enum CommunicationType { } impl CommunicationType { pub fn parse(p0: String) -> CommunicationType { - let normalized = p0.to_lowercase().replace('_', ""); - - match normalized.as_str() { - "watchstream" => CommunicationType::watch_stream, - "calltoken" => CommunicationType::call_token, - "callinvite" => CommunicationType::call_invite, - "calldisconnectuser" => CommunicationType::call_disconnect_user, - "calltimeoutuser" => CommunicationType::call_timeout_user, - "callsetanonymousjoining" => CommunicationType::call_set_anonymous_joining, - "endcall" => CommunicationType::end_call, - "function" => CommunicationType::function, - "update" => CommunicationType::update, - "createuser" => CommunicationType::create_user, - "errorinternal" => CommunicationType::error_internal, - "errorinvaliddata" => CommunicationType::error_invalid_data, - "errorinvaliduserid" => CommunicationType::error_invalid_user_id, - "errorinvalidomikronid" => CommunicationType::error_invalid_omikron_id, - "errornotfound" => CommunicationType::error_not_found, - "errornotauthenticated" => CommunicationType::error_not_authenticated, - "errornoiota" => CommunicationType::error_no_iota, - "errorinvalidchallenge" => CommunicationType::error_invalid_challenge, - "errorinvalidpublickey" => CommunicationType::error_invalid_public_key, - "errorinvalidsecret" => CommunicationType::error_invalid_secret, - "errorinvalidprivatekey" => CommunicationType::error_invalid_private_key, - "errornouserid" => CommunicationType::error_no_user_id, - "errornocallid" => CommunicationType::error_no_call_id, - "errorinvalidcallid" => CommunicationType::error_invalid_call_id, - "success" => CommunicationType::success, - "settingssave" => CommunicationType::settings_save, - "settingsload" => CommunicationType::settings_load, - "settingslist" => CommunicationType::settings_list, - "message" => CommunicationType::message, - "messagesend" => CommunicationType::message_send, - "messagelive" => CommunicationType::message_live, - "messageotheriota" => CommunicationType::message_other_iota, - "messagechunk" => CommunicationType::message_chunk, - "messagesget" => CommunicationType::messages_get, - "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, - "registeriota" => CommunicationType::register_iota, - "registeriotasuccess" => CommunicationType::register_iota_success, - "ping" => CommunicationType::ping, - "pong" => CommunicationType::pong, - "addchat" => CommunicationType::add_chat, - "sendchat" => CommunicationType::send_chat, - "clientchanged" => CommunicationType::client_changed, - "clientconnected" => CommunicationType::client_connected, - "clientdisconnected" => CommunicationType::client_disconnected, - "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, - "rhoupdate" => CommunicationType::rho_update, - - "iotaconnected" => CommunicationType::iota_connected, - "iotadisconnected" => CommunicationType::iota_disconnected, - "userconnected" => CommunicationType::user_connected, - "userdisconnected" => CommunicationType::user_disconnected, - "syncclientiotastatus" => CommunicationType::sync_client_iota_status, - - "getuserdata" => CommunicationType::get_user_data, - "getiotadata" => CommunicationType::get_iota_data, - "iotauserdata" => CommunicationType::iota_user_data, - - "changeuserdata" => CommunicationType::change_user_data, - "changeiotadata" => CommunicationType::change_iota_data, - - "getregister" => CommunicationType::get_register, - "completeregisteruser" => CommunicationType::complete_register_user, - "completeregisteriota" => CommunicationType::complete_register_iota, - "deleteuser" => CommunicationType::delete_user, - "deleteiota" => CommunicationType::delete_iota, - - "startregister" => CommunicationType::start_register, - "completeregister" => CommunicationType::complete_register, - - _ => CommunicationType::error, + for datatype in CommunicationType::iter() { + if datatype.to_string().to_lowercase().replace('_', "") + == p0.to_lowercase().replace('_', "") + { + return datatype; + } } + CommunicationType::error + } + pub fn to_string(&self) -> String { + return format!("{:?}", self); } } diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs index 8b6768f..066149a 100644 --- a/src/util/crypto_util.rs +++ b/src/util/crypto_util.rs @@ -1,805 +1,3 @@ -version = "0.1.0" -dependencies = [ - "aes", - "aes-gcm", - "ansi_term", - "async-tungstenite", - "axum", - "base64 0.22.1", - "block-modes", - "bytes", - "cbc", - "chacha20poly1305", - "chrono", - "cmake", - "crossterm", - "futures-util", - "hex", - "hkdf", - "http 1.4.0", - "hyper", - "json", - -[[package]] -name = "block-modes" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2211b0817f061502a8dd9f11a37e879e79763e3c698d2418cf824d8cb2f21e" - -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher", -] - -[[package]] -name = "cc" -version = "1.2.52" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - -[[package]] -name = "chrono" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" - "crypto-common", - "inout", - "zeroize", -] - -[[package]] - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "block-padding", - "generic-array", -] - - -[[package]] -name = "poly1305" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" -dependencies = [ - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -aes-gcm = "0.10.3" -tokio-native-tls = "0.3.1" -hkdf = "0.12.4" -chacha20poly1305 = "0.10.1" -block-modes = "0.9.1" -cbc = "0.1.2" -aes = "0.8.4" - - get_user_data, - get_iota_data, - iota_user_data, - - change_user_data, - change_iota_data, - "getuserdata" => CommunicationType::get_user_data, - "getiotadata" => CommunicationType::get_iota_data, - "iotauserdata" => CommunicationType::iota_user_data, - - "changeuserdata" => CommunicationType::change_user_data, - "changeiotadata" => CommunicationType::change_iota_data, -#[derive(Debug, Clone)] -pub struct CommunicationValue { - pub id: Uuid, - pub comm_type: CommunicationType, - pub sender: i64, - pub receiver: i64, - pub data: HashMap, - id: Uuid, - comm_type: CommunicationType, - sender: i64, - receiver: i64, - data: HashMap, -} - -#[allow(dead_code)] - } - - pub(crate) fn is_type(&self, p0: CommunicationType) -> bool { - pub fn get_type(&self) -> CommunicationType { - self.comm_type.clone() - } - pub fn is_type(&self, p0: CommunicationType) -> bool { - self.comm_type == p0 - } - pub fn to_json(&self) -> JsonValue { -use json::{JsonValue, number::Number}; -use once_cell::sync::Lazy; -use std::{ - collections::HashMap, - env, - sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - }, - time::Duration, -}; -use std::{collections::HashMap, env, sync::Arc, time::Duration}; -use tokio::{ - net::TcpStream, - sync::{Mutex, RwLock, mpsc}, - get_private_key, log, log_in, log_out, - rho::rho_manager::{self, RHO_CONNECTIONS}, - util::crypto_helper::{decrypt, load_public_key}, - util::crypto_helper::{decrypt_b64, secret_key_to_base64}, - util::logger::PrintType, -}; -use crate::{log_err, util::crypto_helper::secret_key_to_base64}; -use crate::{log_err, util::crypto_helper::load_public_key}; - -pub static WAITING_TASKS: Lazy< - DashMap, CommunicationValue) -> bool + Send + Sync>>, -> = Lazy::new(DashMap::new); - -/// Flag to ensure the connection loop is only started once. -static CONNECTION_LOOP_STARTED: AtomicBool = AtomicBool::new(false); - -static GENERIC_TASK: Lazy< - Mutex, CommunicationValue) -> bool + Send + Sync>>>, -> = Lazy::new(|| Mutex::new(None)); - -static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { - let conn = Arc::new(OmegaConnection::new()); - if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { - let conn_clone = conn.clone(); - tokio::spawn(async move { - conn_clone.connect_internal(0).await; - }); - } - let conn_clone = conn.clone(); - tokio::spawn(async move { - conn_clone.connect_internal(0).await; - }); - conn -}); - - } - pub fn connect(self: Arc) { - if !CONNECTION_LOOP_STARTED.swap(true, Ordering::SeqCst) { - let cloned_self = self.clone(); - tokio::spawn(async move { - cloned_self.connect_internal(0).await; - }); - } - let cloned_self = self.clone(); - tokio::spawn(async move { - cloned_self.connect_internal(0).await; - }); - } - async fn connect_internal(self: Arc, mut retry: usize) { - loop { - })?; - - let server_pub_key_obj = load_public_key(server_pub_key).ok_or("Failed to load public key".to_string())?; - let server_pub_key_obj = load_public_key(server_pub_key).unwrap(); - - let decrypted_challenge = decrypt( - get_private_key(), - server_pub_key_obj, - let decrypted_challenge = decrypt_b64( - &secret_key_to_base64(&get_private_key()), - server_pub_key, - challenge, - ) - .map_err(|e| { - } - - if let Some(accepted) = final_cv.get_data(DataTypes::accepted).and_then(|v| v.as_bool()) { - if !accepted { - log_err!(PrintType::Omega, "Omega did not accept identification."); - return false; - } - } else { - log_err!(PrintType::Omega, "Omega response did not contain 'accepted' field."); - return false; - } - - tokio::spawn(async move { - let mut connected_iota_ids: Vec = Vec::new(); - let mut connected_user_ids: Vec = Vec::new(); - } - let msg_id = cv.get_id(); - log_in!(PrintType::Omikron, "{}", &cv.to_json().to_string()); - log_in!(PrintType::Omega, "{}", &cv.to_json().to_string()); - // Handle waiting tasks - if let Some(task) = WAITING_TASKS.remove(&msg_id) { - if (task.1)(self.clone(), cv.clone()) { - // continue in the read_loop - continue; - } - } else { - // Handle generic task - let generic_task_option = GENERIC_TASK.lock().await; - if let Some(generic_task) = generic_task_option.as_ref() { - if generic_task(self.clone(), cv.clone()) { - // continue in the read_loop - } - } - } - } - #[allow(non_snake_case)] - Some(Ok(Message::Close(_))) | None => break, - Some(Ok(Message::Close(_))) | None => continue, - Some(Err(_)) => break, - _ => {} - } -use async_tungstenite::tungstenite::Message; -use async_tungstenite::{WebSocketReceiver, WebSocketSender}; -use json::JsonValue; -use json::number::Number; -use rand::Rng; -use rand::distributions::Alphanumeric; -use std::sync::{Arc, Weak}; -use std::time::Duration; -use tokio::sync::RwLock; -use tokio_util::compat::Compat; -use tungstenite::Utf8Bytes; -use crate::calls::call_manager; -use crate::omega::omega_connection::{WAITING_TASKS, get_omega_connection}; -use crate::util::crypto_helper::{load_public_key, public_key_to_base64}; -use crate::util::crypto_util::{DataFormat, SecurePayload}; -use crate::util::logger::PrintType; -use crate::{ - // calls::call_manager::CallManager, - omega::omega_connection::OmegaConnection, -}; -use crate::{log_in, log_out}; -use crate::{get_private_key, get_public_key, log_in, log_out}; - -/// ClientConnection represents a WebSocket connection from a client device -pub struct ClientConnection { - /// WebSocket session - pub sender: Arc>>>, - pub receiver: Arc>>>, - /// User ID associated with this client - pub user_id: Arc>, - /// Whether this connection has been identified/authenticated - pub identified: Arc>, - /// Ping latency tracking - identified: Arc>, - challenged: Arc>, - challenge: Arc>, - pub ping: Arc>, - /// Weak reference to RhoConnection to avoid circular references - pub rho_connection: Arc>>>, - /// List of user IDs this client is interested in receiving updates about - pub_key: Arc>>>, - pub rho_connection: Arc>>>, - pub interested_users: Arc>>, -} - - user_id: Arc::new(RwLock::new(0)), - identified: Arc::new(RwLock::new(false)), - challenged: Arc::new(RwLock::new(false)), - challenge: Arc::new(RwLock::new(String::new())), - ping: Arc::new(RwLock::new(-1)), - pub_key: Arc::new(RwLock::new(None)), - rho_connection: Arc::new(RwLock::new(None)), - interested_users: Arc::new(RwLock::new(Vec::new())), - }) - } - - /// 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 - } - self.rho_connection.read().await.clone() - } - - /// Send a string message to the client - tokio::spawn(async move { - let cv = CommunicationValue::from_json(&message); - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; - } - log_in!(PrintType::Client, "{}", &cv.to_json().to_string()); - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; - - // Handle identification - if cv.is_type(CommunicationType::identification) && !self.is_identified().await { - self.handle_identification(Arc::clone(&self), cv).await; - if !identified && cv.is_type(CommunicationType::identification) { - let user_id = cv - .get_data(DataTypes::user_id) - .and_then(|v| v.as_i64()) - .unwrap_or(0); - if user_id == 0 { - log_out!(PrintType::Client, "Invalid USER ID"); - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; - self.close().await; - return; - } - - *self.user_id.write().await = user_id; - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, JsonValue::from(user_id)); - - let response_cv = get_omega_connection() - .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) - .await; - - if let Ok(response_cv) = response_cv { - if !response_cv.is_type(CommunicationType::get_user_data) { - self.send_error_response(&cv.get_id(), CommunicationType::error_internal) - .await; - self.close().await; - return; - } - - let base64_pub = response_cv - .get_data(DataTypes::public_key) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - let pub_key = match load_public_key(base64_pub) { - Some(pk) => pk, - None => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_public_key, - ) - .await; - self.close().await; - return; - } - }; - - *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge.clone(); - - let encrypted_challenge = - SecurePayload::new(challenge, DataFormat::Raw, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - *self.identified.write().await = true; - - let challenge_msg = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data_str( - DataTypes::public_key, - public_key_to_base64(&get_public_key()), - ) - .add_data_str(DataTypes::challenge, encrypted_challenge); - - self.send_message(&challenge_msg).await; - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_internal) - .await; - self.close().await; - return; - } - - return; - } - - if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { - let client_response = cv - .get_data(DataTypes::challenge) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - if client_response == *self.challenge.read().await { - *self.challenged.write().await = true; - - let user_id = self.get_user_id().await; - - 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_no_iota, - ) - .await; - return; - } - }; - - // Set identification data - { - let mut user_id_guard = self.user_id.write().await; - *user_id_guard = user_id; - } - { - let mut identified_guard = self.identified.write().await; - *identified_guard = true; - } - *self.rho_connection.write().await = Some(Arc::clone(&rho_connection)); - - let response = - CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_not_authenticated, - ) - .await; - self.close().await; - return; - } - return; - } - - if !self.is_identified().await { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) - .await; - self.close().await; - return; - } - - } - - /// Handle identification message - async fn handle_identification(&self, sarc: Arc, cv: CommunicationValue) { - // Extract user ID - let user_id: i64 = match cv.get_data(DataTypes::user_id) { - Some(id_str) => id_str.as_i64().unwrap_or(0), - None => { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_user_id) - .await; - return; - } - }; - - // Validate private key - if let Some(private_key_hash) = cv.get_data(DataTypes::private_key_hash) { - println!("private_key_hash: {}", private_key_hash); - let is_valid = true; // NO VALIDATION, - // SWAP TO AUTH VIA CHALLENGE - // auth_connector::is_private_key_valid(user_id, &private_key_hash.to_string()).await; - - if !is_valid { - println!("Invalid private key"); - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_private_key, - ) - .await; - return; - } - } else { - log_in!(PrintType::Client, "Missing private key"); - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_private_key) - .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_no_iota) - .await; - return; - } - }; - - // Set identification data - { - let mut user_id_guard = self.user_id.write().await; - *user_id_guard = user_id; - } - { - let mut identified_guard = self.identified.write().await; - *identified_guard = true; - } - - self.set_rho_connection(Arc::downgrade(&rho_connection)) - .await; - - rho_connection.add_client_connection(Arc::from(sarc)).await; - - 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, cv: CommunicationValue) { - // Update our ping if provided - user_id: Arc::clone(&self.user_id), - identified: Arc::clone(&self.identified), - challenged: Arc::clone(&self.challenged), - challenge: Arc::clone(&self.challenge), - ping: Arc::clone(&self.ping), - pub_key: Arc::clone(&self.pub_key), - rho_connection: Arc::clone(&self.rho_connection), - interested_users: Arc::clone(&self.interested_users), - } -use crate::util::crypto_helper::load_public_key; -use crate::util::crypto_helper::public_key_to_base64; -use crate::util::crypto_util::DataFormat; -use crate::util::crypto_util::SecurePayload; -use crate::util::logger::PrintType; -use async_tungstenite::WebSocketReceiver; -use async_tungstenite::WebSocketSender; -}; -use tokio::sync::RwLock; -use tokio::sync::mpsc; -use tokio_util::compat::Compat; -use tungstenite::Utf8Bytes; -use uuid::Uuid; -use warp::filters::method::get; -use x448::PublicKey; - -use super::{rho_connection::RhoConnection, rho_manager}; - } - - log_in!(PrintType::Iota, "{}", cv.to_json().to_string()); - - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; - - .unwrap_or(0); - if iota_id == 0 { - log_out!(PrintType::Iota, "Invalid IOTA ID"); - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; - self.close().await; - } - - let user_ids_json = cv - .get_data(DataTypes::user_ids) - .unwrap_or(&JsonValue::Null) - .clone(); - let mut user_ids = Vec::new(); - if let JsonValue::Array(ids) = user_ids_json { - for id_val in ids { - if let Some(id) = id_val.as_i64() { - user_ids.push(id); - } - } - } - - *self.iota_id.write().await = iota_id; - *self.user_ids.write().await = user_ids; - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - - let encrypted_challenge = - encrypt(get_private_key(), pub_key, &challenge).unwrap_or_default(); - SecurePayload::new(&challenge, DataFormat::Base64, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - *self.identified.write().await = true; - - - let iota_id = self.get_iota_id().await; - let user_ids = self.get_user_ids().await; - - let mut validated_user_ids: Vec = Vec::new(); - for user_id in user_ids { - validated_user_ids.push(user_id); - } - - if rho_manager::contains_iota(iota_id).await { - if let Some(existing_rho) = rho_manager::get_rho_by_iota(iota_id).await { - } - - // Inform Omega & Verify Users - let iota_users_cv = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::iota_connected).add_data( - DataTypes::iota_id, - JsonValue::from(self.get_iota_id().await), - ), - Some(Duration::from_secs(20)), - ) - .await; - - let mut user_ids: Vec = Vec::new(); - if let Ok(iota_users_cv) = iota_users_cv { - if !iota_users_cv.is_type(CommunicationType::iota_user_data) { - log_err!( - PrintType::Omikron, - "Invalid communication type {:?}", - iota_users_cv.get_type() - ); - return; - } - let val_user_ids = iota_users_cv.get_data(DataTypes::user_ids).unwrap().clone(); - - match val_user_ids { - JsonValue::Array(arr) => { - for item in arr { - if let JsonValue::Number(_) = item { - user_ids.push(item.as_i64().unwrap_or(0)); - } - } - } - _ => {} - } - } else { - log_err!(PrintType::Omikron, "Failed to retrieve user IDs"); - } - log_in!(PrintType::General, "User IDs: {:?}", user_ids.clone()); - - *self.user_ids.write().await = user_ids.clone(); - let rho_connection = - Arc::new(RhoConnection::new(self.clone(), validated_user_ids.clone()).await); - Arc::new(RhoConnection::new(self.clone(), user_ids.clone()).await); - - self.set_rho_connection(Arc::downgrade(&rho_connection)) - .await; - - let mut str = String::new(); - for id in &validated_user_ids { - for id in &user_ids { - str.push_str(&format!(",{}", id)); - } - if !str.is_empty() { - .with_id(cv.get_id()) - .add_data_str(DataTypes::accepted_ids, str) - .add_data_str(DataTypes::accepted, validated_user_ids.len().to_string()), - .add_data_str(DataTypes::accepted, user_ids.len().to_string()), - ) - .await; - } else { - } - } - pub async fn await_response( - &self, - cv: &CommunicationValue, - timeout_duration: Option, - ) -> Result { - let (tx, mut rx) = mpsc::channel(1); - let msg_id = cv.get_id(); - - let task_tx = tx.clone(); - self.waiting_tasks.insert( - msg_id, - Box::new(move |_, response_cv| { - let inner_tx = task_tx.clone(); - tokio::spawn(async move { - if let Err(e) = inner_tx.send(response_cv).await { - log_err!( - PrintType::Iota, - "Failed to send response back to awaiter: {}", - e - ); - } - }); - true - }), - ); - - self.send_message(cv).await; - - let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); - - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response_cv)) => Ok(response_cv), - Ok(None) => Err("Failed to receive response, channel was closed.".to_string()), - Err(_) => { - self.waiting_tasks.remove(&msg_id); - Err(format!( - "Request timed out after {} seconds.", - timeout.as_secs() - )) - } - } - } -} - -impl std::fmt::Debug for IotaConnection { - }; - - // Notify OmegaConnection about the new Iota - OmegaConnection::connect_iota(rho_connection.get_iota_id().await, user_ids).await; - - rho_connection - } - - let connections = self.client_connections.read().await; - for connection in connections.iter() { - if connection.get_user_id().await == cv.receiver { - if connection.get_user_id().await == cv.get_receiver() { - connection.send_message(&cv).await; - } - } -pub mod config_util; -pub mod crypto_helper; -pub mod crypto_util; -pub mod file_util; -pub mod logger; - use aes_gcm::{ Aes256Gcm, Nonce, aead::{Aead, KeyInit, Payload}, diff --git a/src/util/mod.rs b/src/util/mod.rs index 1cd66a6..1897223 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,4 @@ pub mod crypto_helper; +pub mod crypto_util; pub mod file_util; pub mod logger; From ae67a8dad652d68cd6142cd16e2d381e35e620b9 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 16:18:59 +0100 Subject: [PATCH 029/220] link shorting --- src/data/communication.rs | 6 +++ src/server/mod.rs | 1 + src/server/omikron_connection.rs | 16 +++++++ src/server/server.rs | 17 +++++++ src/server/short_link.rs | 77 ++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 src/server/short_link.rs diff --git a/src/data/communication.rs b/src/data/communication.rs index fd57fe1..8cf9188 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -13,6 +13,9 @@ pub enum DataTypes { accepted_ids, uuid, register_id, + + link, + settings, settings_name, chat_partner_id, @@ -124,6 +127,9 @@ pub enum CommunicationType { error_no_call_id, error_invalid_call_id, success, + + shorten_link, + settings_save, settings_load, settings_list, diff --git a/src/server/mod.rs b/src/server/mod.rs index 39df0d8..72ff9f4 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,4 +2,5 @@ pub mod api; pub mod omikron_connection; pub mod omikron_manager; pub mod server; +pub mod short_link; pub mod socket; diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index b2ee2c7..9e8f9d5 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,4 +1,5 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::server::short_link::add_short_link; use crate::sql::connection_status::ConnectionType; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{self}; @@ -472,6 +473,21 @@ impl OmikronConnection { } } } + + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link) { + if let Some(link) = link.as_str() { + if let Ok(short_link) = add_short_link(link).await { + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, JsonValue::String(short_link)); + self.send_message(&response).await; + return; + } + } + } + } + let response = CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); self.send_message(&response).await; diff --git a/src/server/server.rs b/src/server/server.rs index 6c5102b..09d41ed 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,5 +1,6 @@ use crate::log; use crate::server::api; +use crate::server::short_link::get_short_link; use crate::server::socket; use crate::util::file_util::load_file_buf; @@ -126,6 +127,22 @@ impl Service> for HttpService { }; Ok(api::handle(&path, headers.clone(), body_string).await) + } else if path.starts_with("/direct") { + let short = path.split('/').nth(2).unwrap_or_default(); + if let Ok(long) = get_short_link(short).await { + let response = HttpResponse::builder() + .status(StatusCode::FOUND) + .header("Location", long) + .body(Full::new(Bytes::from(""))) + .unwrap(); + Ok(response) + } else { + let response = HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from("Short link not found"))) + .unwrap(); + Ok(response) + } } else { let response = HttpResponse::builder() .status(StatusCode::BAD_REQUEST) diff --git a/src/server/short_link.rs b/src/server/short_link.rs new file mode 100644 index 0000000..5e28cfa --- /dev/null +++ b/src/server/short_link.rs @@ -0,0 +1,77 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::{Rng, thread_rng}; + +static LINKS: Lazy> = Lazy::new(DashMap::new); + +const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ#1234567890"; + +pub async fn add_short_link(long: &str) -> Result { + let raw = generate_unique_short_link().await; + LINKS.insert(raw.clone(), long.to_string()); + + Ok(format!( + "omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )) +} + +async fn generate_unique_short_link() -> String { + loop { + let short = generate_short_link().await; + if !LINKS.contains_key(&short) { + return short; + } + } +} + +pub async fn generate_short_link() -> String { + let len = short_length(); + + let mut rng = thread_rng(); + (0..len) + .map(|_| { + let idx = rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect() +} + +pub async fn get_short_link(short: &str) -> Result { + let normalized = normalize_short(short); + + LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) +} + +/* ---------------- helpers ---------------- */ + +fn short_length() -> usize { + let count = LINKS.len(); + + match count { + 0..=1_999 => 4, + 2_000..=999_999 => 8, + _ => 12, + } +} + +fn format_with_dashes(s: &str) -> String { + s.chars() + .collect::>() + .chunks(4) + .map(|c| c.iter().collect::()) + .collect::>() + .join("-") +} + +fn normalize_short(input: &str) -> String { + input + .chars() + .filter(|c| *c != '-') + .map(|c| match c { + 'Q' | 'O' => '0', + 'I' => 'l', + _ => c, + }) + .collect() +} From 026a91fc4ef9a50c1f66b9a30112901481155829 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 16:18:59 +0100 Subject: [PATCH 030/220] link shorting --- src/data/communication.rs | 6 +++ src/server/mod.rs | 1 + src/server/omikron_connection.rs | 16 +++++++ src/server/server.rs | 17 +++++++ src/server/short_link.rs | 77 ++++++++++++++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 src/server/short_link.rs diff --git a/src/data/communication.rs b/src/data/communication.rs index fd57fe1..8cf9188 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -13,6 +13,9 @@ pub enum DataTypes { accepted_ids, uuid, register_id, + + link, + settings, settings_name, chat_partner_id, @@ -124,6 +127,9 @@ pub enum CommunicationType { error_no_call_id, error_invalid_call_id, success, + + shorten_link, + settings_save, settings_load, settings_list, diff --git a/src/server/mod.rs b/src/server/mod.rs index 39df0d8..72ff9f4 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -2,4 +2,5 @@ pub mod api; pub mod omikron_connection; pub mod omikron_manager; pub mod server; +pub mod short_link; pub mod socket; diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index b2ee2c7..9e8f9d5 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,4 +1,5 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::server::short_link::add_short_link; use crate::sql::connection_status::ConnectionType; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{self}; @@ -472,6 +473,21 @@ impl OmikronConnection { } } } + + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link) { + if let Some(link) = link.as_str() { + if let Ok(short_link) = add_short_link(link).await { + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, JsonValue::String(short_link)); + self.send_message(&response).await; + return; + } + } + } + } + let response = CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); self.send_message(&response).await; diff --git a/src/server/server.rs b/src/server/server.rs index 6c5102b..09d41ed 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,5 +1,6 @@ use crate::log; use crate::server::api; +use crate::server::short_link::get_short_link; use crate::server::socket; use crate::util::file_util::load_file_buf; @@ -126,6 +127,22 @@ impl Service> for HttpService { }; Ok(api::handle(&path, headers.clone(), body_string).await) + } else if path.starts_with("/direct") { + let short = path.split('/').nth(2).unwrap_or_default(); + if let Ok(long) = get_short_link(short).await { + let response = HttpResponse::builder() + .status(StatusCode::FOUND) + .header("Location", long) + .body(Full::new(Bytes::from(""))) + .unwrap(); + Ok(response) + } else { + let response = HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from("Short link not found"))) + .unwrap(); + Ok(response) + } } else { let response = HttpResponse::builder() .status(StatusCode::BAD_REQUEST) diff --git a/src/server/short_link.rs b/src/server/short_link.rs new file mode 100644 index 0000000..5e28cfa --- /dev/null +++ b/src/server/short_link.rs @@ -0,0 +1,77 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::{Rng, thread_rng}; + +static LINKS: Lazy> = Lazy::new(DashMap::new); + +const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ#1234567890"; + +pub async fn add_short_link(long: &str) -> Result { + let raw = generate_unique_short_link().await; + LINKS.insert(raw.clone(), long.to_string()); + + Ok(format!( + "omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )) +} + +async fn generate_unique_short_link() -> String { + loop { + let short = generate_short_link().await; + if !LINKS.contains_key(&short) { + return short; + } + } +} + +pub async fn generate_short_link() -> String { + let len = short_length(); + + let mut rng = thread_rng(); + (0..len) + .map(|_| { + let idx = rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect() +} + +pub async fn get_short_link(short: &str) -> Result { + let normalized = normalize_short(short); + + LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) +} + +/* ---------------- helpers ---------------- */ + +fn short_length() -> usize { + let count = LINKS.len(); + + match count { + 0..=1_999 => 4, + 2_000..=999_999 => 8, + _ => 12, + } +} + +fn format_with_dashes(s: &str) -> String { + s.chars() + .collect::>() + .chunks(4) + .map(|c| c.iter().collect::()) + .collect::>() + .join("-") +} + +fn normalize_short(input: &str) -> String { + input + .chars() + .filter(|c| *c != '-') + .map(|c| match c { + 'Q' | 'O' => '0', + 'I' => 'l', + _ => c, + }) + .collect() +} From bd49f9499f0983ecfa6168af7774a9b7f3fbd750 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 20:25:34 +0100 Subject: [PATCH 031/220] Link shorting --- src/server/omikron_connection.rs | 30 +++++++++++++++--------------- src/server/server.rs | 4 ++-- src/server/short_link.rs | 16 +++++++++++++--- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 9e8f9d5..011389a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -68,7 +68,7 @@ impl OmikronConnection { message_text ); } - sender.send(message_text).await.unwrap(); + let _ = sender.send(message_text).await; } pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await @@ -208,6 +208,20 @@ impl OmikronConnection { } let omikron_id = self.get_omikron_id().await; + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link) { + if let Some(link) = link.as_str() { + if let Ok(short_link) = add_short_link(link).await { + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, JsonValue::String(short_link)); + self.send_message(&response).await; + return; + } + } + } + } + // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { @@ -474,20 +488,6 @@ impl OmikronConnection { } } - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link) { - if let Some(link) = link.as_str() { - if let Ok(short_link) = add_short_link(link).await { - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, JsonValue::String(short_link)); - self.send_message(&response).await; - return; - } - } - } - } - let response = CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); self.send_message(&response).await; diff --git a/src/server/server.rs b/src/server/server.rs index 09d41ed..d4790f3 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -128,8 +128,8 @@ impl Service> for HttpService { Ok(api::handle(&path, headers.clone(), body_string).await) } else if path.starts_with("/direct") { - let short = path.split('/').nth(2).unwrap_or_default(); - if let Ok(long) = get_short_link(short).await { + let short = path.replace("/direct/", ""); + if let Ok(long) = get_short_link(&short).await { let response = HttpResponse::builder() .status(StatusCode::FOUND) .header("Location", long) diff --git a/src/server/short_link.rs b/src/server/short_link.rs index 5e28cfa..6c7d23d 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -11,7 +11,7 @@ pub async fn add_short_link(long: &str) -> Result { LINKS.insert(raw.clone(), long.to_string()); Ok(format!( - "omega.tensamin.net/direct/{}", + "https://omega.tensamin.net/direct/{}", format_with_dashes(&raw) )) } @@ -38,9 +38,19 @@ pub async fn generate_short_link() -> String { } pub async fn get_short_link(short: &str) -> Result { - let normalized = normalize_short(short); + let key = if short.contains("/") { + short.split("/").nth(1).unwrap_or_default() + } else { + short + }; + let frag = short.replace(key, ""); + let normalized = normalize_short(&key); - LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) + if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { + Ok(format!("{}{}", t, frag)) + } else { + Err(()) + } } /* ---------------- helpers ---------------- */ From 0fe3a5ef1e86401991fae64dc0772f37aafcdc63 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 20:25:34 +0100 Subject: [PATCH 032/220] Link shorting --- src/server/omikron_connection.rs | 30 +++++++++++++++--------------- src/server/server.rs | 4 ++-- src/server/short_link.rs | 16 +++++++++++++--- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 9e8f9d5..011389a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -68,7 +68,7 @@ impl OmikronConnection { message_text ); } - sender.send(message_text).await.unwrap(); + let _ = sender.send(message_text).await; } pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await @@ -208,6 +208,20 @@ impl OmikronConnection { } let omikron_id = self.get_omikron_id().await; + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link) { + if let Some(link) = link.as_str() { + if let Ok(short_link) = add_short_link(link).await { + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, JsonValue::String(short_link)); + self.send_message(&response).await; + return; + } + } + } + } + // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { @@ -474,20 +488,6 @@ impl OmikronConnection { } } - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link) { - if let Some(link) = link.as_str() { - if let Ok(short_link) = add_short_link(link).await { - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, JsonValue::String(short_link)); - self.send_message(&response).await; - return; - } - } - } - } - let response = CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); self.send_message(&response).await; diff --git a/src/server/server.rs b/src/server/server.rs index 09d41ed..d4790f3 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -128,8 +128,8 @@ impl Service> for HttpService { Ok(api::handle(&path, headers.clone(), body_string).await) } else if path.starts_with("/direct") { - let short = path.split('/').nth(2).unwrap_or_default(); - if let Ok(long) = get_short_link(short).await { + let short = path.replace("/direct/", ""); + if let Ok(long) = get_short_link(&short).await { let response = HttpResponse::builder() .status(StatusCode::FOUND) .header("Location", long) diff --git a/src/server/short_link.rs b/src/server/short_link.rs index 5e28cfa..6c7d23d 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -11,7 +11,7 @@ pub async fn add_short_link(long: &str) -> Result { LINKS.insert(raw.clone(), long.to_string()); Ok(format!( - "omega.tensamin.net/direct/{}", + "https://omega.tensamin.net/direct/{}", format_with_dashes(&raw) )) } @@ -38,9 +38,19 @@ pub async fn generate_short_link() -> String { } pub async fn get_short_link(short: &str) -> Result { - let normalized = normalize_short(short); + let key = if short.contains("/") { + short.split("/").nth(1).unwrap_or_default() + } else { + short + }; + let frag = short.replace(key, ""); + let normalized = normalize_short(&key); - LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) + if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { + Ok(format!("{}{}", t, frag)) + } else { + Err(()) + } } /* ---------------- helpers ---------------- */ From a16d7b8a79fba17d3479ede968c8c1e16fb04189 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 20:25:34 +0100 Subject: [PATCH 033/220] Link shorting (no #) --- src/server/omikron_connection.rs | 30 +++++++++++++++--------------- src/server/server.rs | 4 ++-- src/server/short_link.rs | 18 ++++++++++++++---- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 9e8f9d5..011389a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -68,7 +68,7 @@ impl OmikronConnection { message_text ); } - sender.send(message_text).await.unwrap(); + let _ = sender.send(message_text).await; } pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await @@ -208,6 +208,20 @@ impl OmikronConnection { } let omikron_id = self.get_omikron_id().await; + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link) { + if let Some(link) = link.as_str() { + if let Ok(short_link) = add_short_link(link).await { + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, JsonValue::String(short_link)); + self.send_message(&response).await; + return; + } + } + } + } + // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { @@ -474,20 +488,6 @@ impl OmikronConnection { } } - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link) { - if let Some(link) = link.as_str() { - if let Ok(short_link) = add_short_link(link).await { - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, JsonValue::String(short_link)); - self.send_message(&response).await; - return; - } - } - } - } - let response = CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); self.send_message(&response).await; diff --git a/src/server/server.rs b/src/server/server.rs index 09d41ed..d4790f3 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -128,8 +128,8 @@ impl Service> for HttpService { Ok(api::handle(&path, headers.clone(), body_string).await) } else if path.starts_with("/direct") { - let short = path.split('/').nth(2).unwrap_or_default(); - if let Ok(long) = get_short_link(short).await { + let short = path.replace("/direct/", ""); + if let Ok(long) = get_short_link(&short).await { let response = HttpResponse::builder() .status(StatusCode::FOUND) .header("Location", long) diff --git a/src/server/short_link.rs b/src/server/short_link.rs index 5e28cfa..c0ab51d 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -4,14 +4,14 @@ use rand::{Rng, thread_rng}; static LINKS: Lazy> = Lazy::new(DashMap::new); -const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ#1234567890"; +const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; pub async fn add_short_link(long: &str) -> Result { let raw = generate_unique_short_link().await; LINKS.insert(raw.clone(), long.to_string()); Ok(format!( - "omega.tensamin.net/direct/{}", + "https://omega.tensamin.net/direct/{}", format_with_dashes(&raw) )) } @@ -38,9 +38,19 @@ pub async fn generate_short_link() -> String { } pub async fn get_short_link(short: &str) -> Result { - let normalized = normalize_short(short); + let key = if short.contains("/") { + short.split("/").nth(1).unwrap_or_default() + } else { + short + }; + let frag = short.replace(key, ""); + let normalized = normalize_short(&key); - LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) + if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { + Ok(format!("{}{}", t, frag)) + } else { + Err(()) + } } /* ---------------- helpers ---------------- */ From 30caec0921025375fb0c47988dcca477cdc99f19 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 20:25:34 +0100 Subject: [PATCH 034/220] Link shorting (no #) --- src/server/omikron_connection.rs | 30 +++++++++++++++--------------- src/server/server.rs | 4 ++-- src/server/short_link.rs | 18 ++++++++++++++---- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 9e8f9d5..011389a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -68,7 +68,7 @@ impl OmikronConnection { message_text ); } - sender.send(message_text).await.unwrap(); + let _ = sender.send(message_text).await; } pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await @@ -208,6 +208,20 @@ impl OmikronConnection { } let omikron_id = self.get_omikron_id().await; + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link) { + if let Some(link) = link.as_str() { + if let Ok(short_link) = add_short_link(link).await { + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, JsonValue::String(short_link)); + self.send_message(&response).await; + return; + } + } + } + } + // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { @@ -474,20 +488,6 @@ impl OmikronConnection { } } - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link) { - if let Some(link) = link.as_str() { - if let Ok(short_link) = add_short_link(link).await { - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, JsonValue::String(short_link)); - self.send_message(&response).await; - return; - } - } - } - } - let response = CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); self.send_message(&response).await; diff --git a/src/server/server.rs b/src/server/server.rs index 09d41ed..d4790f3 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -128,8 +128,8 @@ impl Service> for HttpService { Ok(api::handle(&path, headers.clone(), body_string).await) } else if path.starts_with("/direct") { - let short = path.split('/').nth(2).unwrap_or_default(); - if let Ok(long) = get_short_link(short).await { + let short = path.replace("/direct/", ""); + if let Ok(long) = get_short_link(&short).await { let response = HttpResponse::builder() .status(StatusCode::FOUND) .header("Location", long) diff --git a/src/server/short_link.rs b/src/server/short_link.rs index 5e28cfa..c0ab51d 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -4,14 +4,14 @@ use rand::{Rng, thread_rng}; static LINKS: Lazy> = Lazy::new(DashMap::new); -const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ#1234567890"; +const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; pub async fn add_short_link(long: &str) -> Result { let raw = generate_unique_short_link().await; LINKS.insert(raw.clone(), long.to_string()); Ok(format!( - "omega.tensamin.net/direct/{}", + "https://omega.tensamin.net/direct/{}", format_with_dashes(&raw) )) } @@ -38,9 +38,19 @@ pub async fn generate_short_link() -> String { } pub async fn get_short_link(short: &str) -> Result { - let normalized = normalize_short(short); + let key = if short.contains("/") { + short.split("/").nth(1).unwrap_or_default() + } else { + short + }; + let frag = short.replace(key, ""); + let normalized = normalize_short(&key); - LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) + if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { + Ok(format!("{}{}", t, frag)) + } else { + Err(()) + } } /* ---------------- helpers ---------------- */ From 88a7603ca19d4dbdc9ea4e4d2ec86f578b7f8bb3 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 21:45:13 +0100 Subject: [PATCH 035/220] micro --- src/data/communication.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 8cf9188..7c1660d 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -45,7 +45,7 @@ pub enum DataTypes { call_id, call_token, untill, - enable, + enabled, start_date, end_date, receiver_id, From dc2fc71a4b365f0ee8bb5bfb9e54e86a89271418 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 20 Jan 2026 21:45:13 +0100 Subject: [PATCH 036/220] micro --- src/data/communication.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 8cf9188..7c1660d 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -45,7 +45,7 @@ pub enum DataTypes { call_id, call_token, untill, - enable, + enabled, start_date, end_date, receiver_id, From d2a4d0c7ae68150512f815b13568ec202d6ab6f0 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:58:43 +0100 Subject: [PATCH 037/220] [Add] Connection Tracking --- src/server/api.rs | 49 +++++++++++------ src/server/omikron_connection.rs | 43 +++++++-------- src/server/omikron_manager.rs | 34 ++++++++++++ src/sql/iota_omikron_tracker.rs | 27 --------- src/sql/mod.rs | 1 - src/sql/sql.rs | 33 ----------- src/sql/user_online_tracker.rs | 94 +++++++++++++++++++------------- 7 files changed, 143 insertions(+), 138 deletions(-) delete mode 100644 src/sql/iota_omikron_tracker.rs diff --git a/src/server/api.rs b/src/server/api.rs index 9d77b5b..a2e40ec 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,11 +1,12 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::get_public_key; +use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; +use crate::sql::user_online_tracker::{ + get_iota_omikron_connections, get_iota_primary_omikron_connection, +}; use crate::{ - sql::{ - iota_omikron_tracker::get_omikron_for_iota, - sql::{get_by_user_id, get_omikron_by_id, get_random_omikron}, - }, + sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, }; use axum::http::HeaderValue; @@ -43,17 +44,33 @@ pub async fn handle( // api/get/omikron/ -> omikron for id (user / iota / omikron) "omikron" => { if path_parts.len() == 3 { - if let Ok((id, public_key, ip_address)) = get_random_omikron().await { - ( - StatusCode::OK, - "application/json", - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - id, public_key, ip_address - ), - ) + if let Ok(omikron_conn) = get_random_omikron().await { + if let Ok((public_key, ip_address)) = + sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_conn.get_omikron_id().await, + public_key, + ip_address + ), + ) + } else { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "text/plain", + "selected an invalid omikron".to_string(), + ) + } } else { - not_found() + ( + StatusCode::NOT_FOUND, + "text/plain", + "couldn't find online omikron".to_string(), + ) } } else if path_parts.len() == 4 { let id = path_parts[3].parse::().unwrap_or(0); @@ -68,7 +85,7 @@ pub async fn handle( id, public_key, ip_address ), ) - } else if let Some(omikron_id) = get_omikron_for_iota(id).await { + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { @@ -86,7 +103,7 @@ pub async fn handle( } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await { - if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { + if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 011389a..5c3d29c 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,4 +1,5 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::server::omikron_manager; use crate::server::short_link::add_short_link; use crate::sql::connection_status::ConnectionType; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; @@ -11,6 +12,7 @@ use dashmap::DashMap; use futures::SinkExt; use futures::stream::SplitSink; use futures::stream::SplitStream; +use futures::task::UnsafeFutureObj; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use json::JsonValue; @@ -183,8 +185,7 @@ impl OmikronConnection { if client_response == *self.challenge.read().await { *self.challenged.write().await = true; - let _ = sql::set_omikron_active(self.get_omikron_id().await, true); - + omikron_manager::add_omikron(self.clone()).await; self.send_message( &CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()) @@ -225,20 +226,18 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id) - .await; + user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id); } return; } if cv.is_type(CommunicationType::user_disconnected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - if let Some(status) = user_online_tracker::get_user_status(user_id).await { + if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( user_id, ConnectionType::UserOffline, status.omikron_id, - ) - .await; + ); } } return; @@ -247,7 +246,7 @@ impl OmikronConnection { if cv.is_type(CommunicationType::iota_connected) { log_in!(PrintType::Omega, "IOTA connected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); let mut user_ids = JsonValue::Array(Vec::new()); if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { for user in users { @@ -256,8 +255,7 @@ impl OmikronConnection { user.0, ConnectionType::UserOffline, omikron_id, - ) - .await; + ); } } else { log_in!(PrintType::General, "SQL error, when loading users for IOTA"); @@ -276,11 +274,11 @@ impl OmikronConnection { if cv.is_type(CommunicationType::iota_disconnected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { let iota_offline = - user_online_tracker::untrack_iota_connection(iota_id, omikron_id).await; + user_online_tracker::untrack_iota_connection(iota_id, omikron_id); if iota_offline { if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids).await; + user_online_tracker::untrack_many_users(&user_ids); } } } @@ -296,8 +294,7 @@ impl OmikronConnection { user_id, ConnectionType::Online, omikron_id, - ) - .await; + ); } } } @@ -306,7 +303,7 @@ impl OmikronConnection { { for iota_id_json in iota_ids { if let Some(iota_id) = iota_id_json.as_i64() { - user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); } } } @@ -365,10 +362,9 @@ impl OmikronConnection { response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); } - let user_status = user_online_tracker::get_user_status(id).await; + let user_status = user_online_tracker::get_user_status(id); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( DataTypes::omikron_connections, @@ -451,10 +447,9 @@ impl OmikronConnection { response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); } - let user_status = user_online_tracker::get_user_status(id).await; + let user_status = user_online_tracker::get_user_status(id); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); if let Some(user_status) = user_status { @@ -506,7 +501,6 @@ impl OmikronConnection { let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( @@ -544,7 +538,6 @@ impl OmikronConnection { let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( @@ -585,7 +578,6 @@ impl OmikronConnection { let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( @@ -904,7 +896,11 @@ impl OmikronConnection { pub async fn close(&self) { let mut sender = self.sender.write().await; if self.is_identified().await { - let _ = sql::set_omikron_active(self.get_omikron_id().await, false); + let omikron_id = self.get_omikron_id().await; + if omikron_id != 0 { + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; + } } let _ = sender.close().await; } @@ -912,6 +908,7 @@ impl OmikronConnection { if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { + omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } } diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index e69de29..8440835 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -0,0 +1,34 @@ +use crate::{log_in, server::omikron_connection::OmikronConnection, util::logger::PrintType}; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::prelude::IteratorRandom; +use std::sync::Arc; + +pub static OMIKRON_CONNECTIONS: Lazy>> = + Lazy::new(|| DashMap::new()); + +pub async fn add_omikron(omikron_conn: Arc) { + OMIKRON_CONNECTIONS.insert(omikron_conn.get_omikron_id().await, omikron_conn); +} + +pub async fn remove_omikron(omikron_id: i64) { + OMIKRON_CONNECTIONS.remove(&omikron_id); +} + +pub async fn get_random_omikron() -> Result, ()> { + log_in!(0, PrintType::Iota, "{}", OMIKRON_CONNECTIONS.len()); + let mut rng = rand::thread_rng(); + if let Some((_, val)) = OMIKRON_CONNECTIONS.clone().into_iter().choose(&mut rng) { + return Ok(val); + } else { + return Err(()); + } +} + +pub async fn get_omikron(omikron_id: i64) -> Option> { + if let Some(omikron) = OMIKRON_CONNECTIONS.get(&omikron_id) { + Some(omikron.clone()) + } else { + None + } +} diff --git a/src/sql/iota_omikron_tracker.rs b/src/sql/iota_omikron_tracker.rs deleted file mode 100644 index cf11310..0000000 --- a/src/sql/iota_omikron_tracker.rs +++ /dev/null @@ -1,27 +0,0 @@ -use once_cell::sync::Lazy; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -static IOTA_OMIKRON_MAP: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); - -pub async fn track_iota_omikron(iota: i64, omikron: i64) { - let mut c = IOTA_OMIKRON_MAP.write().await; - c.insert(iota, omikron); -} - -pub async fn get_omikron_for_iota(iota: i64) -> Option { - let c = IOTA_OMIKRON_MAP.read().await; - c.get(&iota).cloned() -} - -pub async fn untrack_iota(iota: i64) { - let mut c = IOTA_OMIKRON_MAP.write().await; - c.remove(&iota); -} - -pub async fn untrack_by_omikron(omikron: i64) { - let mut c = IOTA_OMIKRON_MAP.write().await; - c.retain(|_, v| *v != omikron); -} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 033fd01..91ba841 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,4 +1,3 @@ pub mod connection_status; -pub mod iota_omikron_tracker; pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs index ec0f1d2..20dd688 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -87,7 +87,6 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS omikrons ( id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - is_active INT(1) NOT NULL DEFAULT 0, public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin @@ -589,23 +588,6 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { // OMIKRONS // ========================================================================================== -pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - let row = sqlx::query_as::<_, (i64, Vec, Vec)>("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") - .fetch_optional(pool) - .await?; - - match row { - Some((id, public_key, ip_address)) => Ok(( - id, - String::from_utf8_lossy(&public_key).to_string(), - String::from_utf8_lossy(&ip_address).to_string(), - )), - _ => Err(sqlx::Error::RowNotFound), - } -} - pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -625,18 +607,3 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> _ => Err(sqlx::Error::RowNotFound), } } - -pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - let active = if active { 1 } else { 0 }; - - sqlx::query("UPDATE omikrons SET active = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(active) - .bind(id) - .execute(pool) - .await?; - - Ok(()) -} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 95e94c6..3a1ab42 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,9 +1,7 @@ use crate::sql; use crate::sql::connection_status::ConnectionType; +use dashmap::DashMap; use once_cell::sync::Lazy; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; #[derive(Debug, Clone)] pub struct UserStatus { @@ -11,42 +9,59 @@ pub struct UserStatus { pub omikron_id: i64, } +// IotaID -> Primary OmikronID +static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); + // IotaID -> Vec -static IOTA_OMIKRON_CONNECTIONS: Lazy>>>> = - Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); +static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); // UserID -> UserStatus -static USER_STATUS_MAP: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); +static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); -pub async fn track_iota_connection(iota_id: i64, omikron_id: i64) { - let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; - let connections = iota_map.entry(iota_id).or_default(); - if !connections.contains(&omikron_id) { - connections.push(omikron_id); +pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { + let mut entry = IOTA_OMIKRON_CONNECTIONS + .entry(iota_id) + .or_insert_with(Vec::new); + + if !entry.contains(&omikron_id) { + entry.push(omikron_id); + } + + if primary { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); } } -pub async fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; - if let Some(connections) = iota_map.get_mut(&iota_id) { +pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { + if let Some(mut connections) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { connections.retain(|&id| id != omikron_id); + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(&iota_id) + .map(|p| *p == omikron_id) + .unwrap_or(false) + { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + if connections.is_empty() { - iota_map.remove(&iota_id); - return true; // Iota is now offline + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + return true; } } false } -pub async fn get_iota_omikron_connections(iota_id: i64) -> Option> { - let iota_map = IOTA_OMIKRON_CONNECTIONS.read().await; - iota_map.get(&iota_id).cloned() +pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { + IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) } -pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { - let mut user_map = USER_STATUS_MAP.write().await; - user_map.insert( +pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { + IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) +} + +pub fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { + USER_STATUS_MAP.insert( user_id, UserStatus { connection_type: status, @@ -55,31 +70,34 @@ pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: ); } -pub async fn get_user_status(user_id: i64) -> Option { - let user_map = USER_STATUS_MAP.read().await; - user_map.get(&user_id).cloned() +pub fn get_user_status(user_id: i64) -> Option { + USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } -pub async fn untrack_user(user_id: i64) { - let mut user_map = USER_STATUS_MAP.write().await; - user_map.remove(&user_id); +pub fn untrack_user(user_id: i64) { + USER_STATUS_MAP.remove(&user_id); } -pub async fn untrack_many_users(user_ids: &[i64]) { - let mut user_map = USER_STATUS_MAP.write().await; +pub fn untrack_many_users(user_ids: &[i64]) { for user_id in user_ids { - user_map.remove(user_id); + USER_STATUS_MAP.remove(user_id); } } pub async fn untrack_omikron(omikron_id: i64) { - let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; - let mut user_map = USER_STATUS_MAP.write().await; - let mut offline_iotas = Vec::new(); - iota_map.retain(|iota_id, connections| { + IOTA_OMIKRON_CONNECTIONS.retain(|iota_id, connections| { connections.retain(|id| *id != omikron_id); + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(iota_id) + .map(|p| *p == omikron_id) + .unwrap_or(false) + { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); + } + if connections.is_empty() { offline_iotas.push(*iota_id); false @@ -88,12 +106,12 @@ pub async fn untrack_omikron(omikron_id: i64) { } }); - user_map.retain(|_, status| status.omikron_id != omikron_id); + USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); for iota_id in offline_iotas { if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { for user in users { - user_map.remove(&user.0); + USER_STATUS_MAP.remove(&user.0); } } } From d3f2f09b7b7f0d862831bfd373d813e908b75632 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 29 Jan 2026 10:58:43 +0100 Subject: [PATCH 038/220] [Add] Connection Tracking --- src/server/api.rs | 49 +++++++++++------ src/server/omikron_connection.rs | 43 +++++++-------- src/server/omikron_manager.rs | 34 ++++++++++++ src/sql/iota_omikron_tracker.rs | 27 --------- src/sql/mod.rs | 1 - src/sql/sql.rs | 33 ----------- src/sql/user_online_tracker.rs | 94 +++++++++++++++++++------------- 7 files changed, 143 insertions(+), 138 deletions(-) delete mode 100644 src/sql/iota_omikron_tracker.rs diff --git a/src/server/api.rs b/src/server/api.rs index 9d77b5b..a2e40ec 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,11 +1,12 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::get_public_key; +use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; +use crate::sql::user_online_tracker::{ + get_iota_omikron_connections, get_iota_primary_omikron_connection, +}; use crate::{ - sql::{ - iota_omikron_tracker::get_omikron_for_iota, - sql::{get_by_user_id, get_omikron_by_id, get_random_omikron}, - }, + sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, }; use axum::http::HeaderValue; @@ -43,17 +44,33 @@ pub async fn handle( // api/get/omikron/ -> omikron for id (user / iota / omikron) "omikron" => { if path_parts.len() == 3 { - if let Ok((id, public_key, ip_address)) = get_random_omikron().await { - ( - StatusCode::OK, - "application/json", - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - id, public_key, ip_address - ), - ) + if let Ok(omikron_conn) = get_random_omikron().await { + if let Ok((public_key, ip_address)) = + sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await + { + ( + StatusCode::OK, + "application/json", + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_conn.get_omikron_id().await, + public_key, + ip_address + ), + ) + } else { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "text/plain", + "selected an invalid omikron".to_string(), + ) + } } else { - not_found() + ( + StatusCode::NOT_FOUND, + "text/plain", + "couldn't find online omikron".to_string(), + ) } } else if path_parts.len() == 4 { let id = path_parts[3].parse::().unwrap_or(0); @@ -68,7 +85,7 @@ pub async fn handle( id, public_key, ip_address ), ) - } else if let Some(omikron_id) = get_omikron_for_iota(id).await { + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { @@ -86,7 +103,7 @@ pub async fn handle( } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await { - if let Some(omikron_id) = get_omikron_for_iota(iota_id).await { + if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 011389a..5c3d29c 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,4 +1,5 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::server::omikron_manager; use crate::server::short_link::add_short_link; use crate::sql::connection_status::ConnectionType; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; @@ -11,6 +12,7 @@ use dashmap::DashMap; use futures::SinkExt; use futures::stream::SplitSink; use futures::stream::SplitStream; +use futures::task::UnsafeFutureObj; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use json::JsonValue; @@ -183,8 +185,7 @@ impl OmikronConnection { if client_response == *self.challenge.read().await { *self.challenged.write().await = true; - let _ = sql::set_omikron_active(self.get_omikron_id().await, true); - + omikron_manager::add_omikron(self.clone()).await; self.send_message( &CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()) @@ -225,20 +226,18 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id) - .await; + user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id); } return; } if cv.is_type(CommunicationType::user_disconnected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - if let Some(status) = user_online_tracker::get_user_status(user_id).await { + if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( user_id, ConnectionType::UserOffline, status.omikron_id, - ) - .await; + ); } } return; @@ -247,7 +246,7 @@ impl OmikronConnection { if cv.is_type(CommunicationType::iota_connected) { log_in!(PrintType::Omega, "IOTA connected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); let mut user_ids = JsonValue::Array(Vec::new()); if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { for user in users { @@ -256,8 +255,7 @@ impl OmikronConnection { user.0, ConnectionType::UserOffline, omikron_id, - ) - .await; + ); } } else { log_in!(PrintType::General, "SQL error, when loading users for IOTA"); @@ -276,11 +274,11 @@ impl OmikronConnection { if cv.is_type(CommunicationType::iota_disconnected) { if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { let iota_offline = - user_online_tracker::untrack_iota_connection(iota_id, omikron_id).await; + user_online_tracker::untrack_iota_connection(iota_id, omikron_id); if iota_offline { if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids).await; + user_online_tracker::untrack_many_users(&user_ids); } } } @@ -296,8 +294,7 @@ impl OmikronConnection { user_id, ConnectionType::Online, omikron_id, - ) - .await; + ); } } } @@ -306,7 +303,7 @@ impl OmikronConnection { { for iota_id_json in iota_ids { if let Some(iota_id) = iota_id_json.as_i64() { - user_online_tracker::track_iota_connection(iota_id, omikron_id).await; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); } } } @@ -365,10 +362,9 @@ impl OmikronConnection { response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); } - let user_status = user_online_tracker::get_user_status(id).await; + let user_status = user_online_tracker::get_user_status(id); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( DataTypes::omikron_connections, @@ -451,10 +447,9 @@ impl OmikronConnection { response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); } - let user_status = user_online_tracker::get_user_status(id).await; + let user_status = user_online_tracker::get_user_status(id); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); if let Some(user_status) = user_status { @@ -506,7 +501,6 @@ impl OmikronConnection { let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( @@ -544,7 +538,6 @@ impl OmikronConnection { let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( @@ -585,7 +578,6 @@ impl OmikronConnection { let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id) - .await .unwrap_or_default(); response = response.add_data( @@ -904,7 +896,11 @@ impl OmikronConnection { pub async fn close(&self) { let mut sender = self.sender.write().await; if self.is_identified().await { - let _ = sql::set_omikron_active(self.get_omikron_id().await, false); + let omikron_id = self.get_omikron_id().await; + if omikron_id != 0 { + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; + } } let _ = sender.close().await; } @@ -912,6 +908,7 @@ impl OmikronConnection { if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { + omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } } diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index e69de29..8440835 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -0,0 +1,34 @@ +use crate::{log_in, server::omikron_connection::OmikronConnection, util::logger::PrintType}; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::prelude::IteratorRandom; +use std::sync::Arc; + +pub static OMIKRON_CONNECTIONS: Lazy>> = + Lazy::new(|| DashMap::new()); + +pub async fn add_omikron(omikron_conn: Arc) { + OMIKRON_CONNECTIONS.insert(omikron_conn.get_omikron_id().await, omikron_conn); +} + +pub async fn remove_omikron(omikron_id: i64) { + OMIKRON_CONNECTIONS.remove(&omikron_id); +} + +pub async fn get_random_omikron() -> Result, ()> { + log_in!(0, PrintType::Iota, "{}", OMIKRON_CONNECTIONS.len()); + let mut rng = rand::thread_rng(); + if let Some((_, val)) = OMIKRON_CONNECTIONS.clone().into_iter().choose(&mut rng) { + return Ok(val); + } else { + return Err(()); + } +} + +pub async fn get_omikron(omikron_id: i64) -> Option> { + if let Some(omikron) = OMIKRON_CONNECTIONS.get(&omikron_id) { + Some(omikron.clone()) + } else { + None + } +} diff --git a/src/sql/iota_omikron_tracker.rs b/src/sql/iota_omikron_tracker.rs deleted file mode 100644 index cf11310..0000000 --- a/src/sql/iota_omikron_tracker.rs +++ /dev/null @@ -1,27 +0,0 @@ -use once_cell::sync::Lazy; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; - -static IOTA_OMIKRON_MAP: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); - -pub async fn track_iota_omikron(iota: i64, omikron: i64) { - let mut c = IOTA_OMIKRON_MAP.write().await; - c.insert(iota, omikron); -} - -pub async fn get_omikron_for_iota(iota: i64) -> Option { - let c = IOTA_OMIKRON_MAP.read().await; - c.get(&iota).cloned() -} - -pub async fn untrack_iota(iota: i64) { - let mut c = IOTA_OMIKRON_MAP.write().await; - c.remove(&iota); -} - -pub async fn untrack_by_omikron(omikron: i64) { - let mut c = IOTA_OMIKRON_MAP.write().await; - c.retain(|_, v| *v != omikron); -} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 033fd01..91ba841 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,4 +1,3 @@ pub mod connection_status; -pub mod iota_omikron_tracker; pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs index ec0f1d2..20dd688 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -87,7 +87,6 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS omikrons ( id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - is_active INT(1) NOT NULL DEFAULT 0, public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin @@ -589,23 +588,6 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { // OMIKRONS // ========================================================================================== -pub async fn get_random_omikron() -> Result<(i64, String, String), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - let row = sqlx::query_as::<_, (i64, Vec, Vec)>("SELECT id, public_key, ip_address FROM omikrons WHERE is_active = 1 ORDER BY RAND() LIMIT 1") - .fetch_optional(pool) - .await?; - - match row { - Some((id, public_key, ip_address)) => Ok(( - id, - String::from_utf8_lossy(&public_key).to_string(), - String::from_utf8_lossy(&ip_address).to_string(), - )), - _ => Err(sqlx::Error::RowNotFound), - } -} - pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -625,18 +607,3 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> _ => Err(sqlx::Error::RowNotFound), } } - -pub async fn set_omikron_active(id: i64, active: bool) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - let active = if active { 1 } else { 0 }; - - sqlx::query("UPDATE omikrons SET active = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(active) - .bind(id) - .execute(pool) - .await?; - - Ok(()) -} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 95e94c6..3a1ab42 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,9 +1,7 @@ use crate::sql; use crate::sql::connection_status::ConnectionType; +use dashmap::DashMap; use once_cell::sync::Lazy; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; #[derive(Debug, Clone)] pub struct UserStatus { @@ -11,42 +9,59 @@ pub struct UserStatus { pub omikron_id: i64, } +// IotaID -> Primary OmikronID +static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); + // IotaID -> Vec -static IOTA_OMIKRON_CONNECTIONS: Lazy>>>> = - Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); +static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); // UserID -> UserStatus -static USER_STATUS_MAP: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(HashMap::new()))); +static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); -pub async fn track_iota_connection(iota_id: i64, omikron_id: i64) { - let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; - let connections = iota_map.entry(iota_id).or_default(); - if !connections.contains(&omikron_id) { - connections.push(omikron_id); +pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { + let mut entry = IOTA_OMIKRON_CONNECTIONS + .entry(iota_id) + .or_insert_with(Vec::new); + + if !entry.contains(&omikron_id) { + entry.push(omikron_id); + } + + if primary { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); } } -pub async fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; - if let Some(connections) = iota_map.get_mut(&iota_id) { +pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { + if let Some(mut connections) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { connections.retain(|&id| id != omikron_id); + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(&iota_id) + .map(|p| *p == omikron_id) + .unwrap_or(false) + { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + if connections.is_empty() { - iota_map.remove(&iota_id); - return true; // Iota is now offline + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + return true; } } false } -pub async fn get_iota_omikron_connections(iota_id: i64) -> Option> { - let iota_map = IOTA_OMIKRON_CONNECTIONS.read().await; - iota_map.get(&iota_id).cloned() +pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { + IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) } -pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { - let mut user_map = USER_STATUS_MAP.write().await; - user_map.insert( +pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { + IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) +} + +pub fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { + USER_STATUS_MAP.insert( user_id, UserStatus { connection_type: status, @@ -55,31 +70,34 @@ pub async fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: ); } -pub async fn get_user_status(user_id: i64) -> Option { - let user_map = USER_STATUS_MAP.read().await; - user_map.get(&user_id).cloned() +pub fn get_user_status(user_id: i64) -> Option { + USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } -pub async fn untrack_user(user_id: i64) { - let mut user_map = USER_STATUS_MAP.write().await; - user_map.remove(&user_id); +pub fn untrack_user(user_id: i64) { + USER_STATUS_MAP.remove(&user_id); } -pub async fn untrack_many_users(user_ids: &[i64]) { - let mut user_map = USER_STATUS_MAP.write().await; +pub fn untrack_many_users(user_ids: &[i64]) { for user_id in user_ids { - user_map.remove(user_id); + USER_STATUS_MAP.remove(user_id); } } pub async fn untrack_omikron(omikron_id: i64) { - let mut iota_map = IOTA_OMIKRON_CONNECTIONS.write().await; - let mut user_map = USER_STATUS_MAP.write().await; - let mut offline_iotas = Vec::new(); - iota_map.retain(|iota_id, connections| { + IOTA_OMIKRON_CONNECTIONS.retain(|iota_id, connections| { connections.retain(|id| *id != omikron_id); + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(iota_id) + .map(|p| *p == omikron_id) + .unwrap_or(false) + { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); + } + if connections.is_empty() { offline_iotas.push(*iota_id); false @@ -88,12 +106,12 @@ pub async fn untrack_omikron(omikron_id: i64) { } }); - user_map.retain(|_, status| status.omikron_id != omikron_id); + USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); for iota_id in offline_iotas { if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { for user in users { - user_map.remove(&user.0); + USER_STATUS_MAP.remove(&user.0); } } } From 88f5752af56d9853e5606a7a2856b28177aa68e7 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:09:51 +0100 Subject: [PATCH 039/220] [Cleaned] --- src/server/api.rs | 58 +---- src/server/omikron_connection.rs | 17 +- src/server/server.rs | 37 +++- src/sql/connection_status.rs | 47 ++--- src/sql/sql.rs | 2 +- src/sql/user_online_tracker.rs | 18 +- src/util/crypto_helper.rs | 16 +- src/util/crypto_util.rs | 1 - src/util/file_util.rs | 350 ------------------------------- src/util/logger.rs | 3 +- src/util/mod.rs | 1 - 11 files changed, 86 insertions(+), 464 deletions(-) delete mode 100644 src/util/file_util.rs diff --git a/src/server/api.rs b/src/server/api.rs index a2e40ec..10be9fd 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -2,9 +2,7 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataType use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; -use crate::sql::user_online_tracker::{ - get_iota_omikron_connections, get_iota_primary_omikron_connection, -}; +use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, @@ -19,12 +17,12 @@ use json::number::Number; pub async fn handle( path: &str, - headers: HeaderMap, + _headers: HeaderMap, body_string: Option, ) -> HttpResponse> { let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); - let body: Option = if body_string.is_some() { + let _body: Option = if body_string.is_some() { if let Ok(body_json) = json::parse(&body_string.unwrap()) { Some(body_json) } else { @@ -37,7 +35,7 @@ pub async fn handle( // get/ // omikron/ // id/ - let (status, content, body_text) = if path_parts.len() >= 2 { + let (status, body_text) = if path_parts.len() >= 2 { match path_parts[1] { "get" => match path_parts[2] { // api/get/omikron -> any omikron @@ -50,7 +48,6 @@ pub async fn handle( { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", omikron_conn.get_omikron_id().await, @@ -61,14 +58,12 @@ pub async fn handle( } else { ( StatusCode::INTERNAL_SERVER_ERROR, - "text/plain", "selected an invalid omikron".to_string(), ) } } else { ( StatusCode::NOT_FOUND, - "text/plain", "couldn't find online omikron".to_string(), ) } @@ -79,7 +74,6 @@ pub async fn handle( } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", id, public_key, ip_address @@ -91,7 +85,6 @@ pub async fn handle( { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", omikron_id, public_key, ip_address @@ -109,7 +102,6 @@ pub async fn handle( { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", omikron_id, public_key, ip_address @@ -171,11 +163,10 @@ pub async fn handle( DataTypes::sub_end, JsonValue::Number(Number::from(sub_end)), ); - (StatusCode::OK, "application/json", cv.to_json().to_string()) + (StatusCode::OK, cv.to_json().to_string()) } else { ( StatusCode::OK, - "application/json", CommunicationValue::new(CommunicationType::error_not_found) .to_json() .to_string(), @@ -184,11 +175,7 @@ pub async fn handle( } } } - "public_key" => ( - StatusCode::OK, - "application/json", - public_key_to_base64(&get_public_key()), - ), + "public_key" => (StatusCode::OK, public_key_to_base64(&get_public_key())), "user" => { if path_parts.len() != 4 { bad_request() @@ -247,11 +234,10 @@ pub async fn handle( base64::engine::general_purpose::STANDARD.encode(avatar), ); } - (StatusCode::OK, "application/json", cv.to_json().to_string()) + (StatusCode::OK, cv.to_json().to_string()) } else { ( StatusCode::OK, - "application/json", CommunicationValue::new(CommunicationType::error_not_found) .to_json() .to_string(), @@ -278,31 +264,9 @@ pub async fn handle( let body = Full::new(Bytes::from(body_text.to_string())); HttpResponse::builder().status(status).body(body).unwrap() } -pub fn bad_request() -> (StatusCode, &'static str, String) { - ( - StatusCode::BAD_REQUEST, - "text/text", - "400 Bad Request".to_string(), - ) +pub fn bad_request() -> (StatusCode, String) { + (StatusCode::BAD_REQUEST, "400 Bad Request".to_string()) } -pub fn unauthorized() -> (StatusCode, &'static str, String) { - ( - StatusCode::UNAUTHORIZED, - "text/text", - "401 Unauthorized".to_string(), - ) -} -pub fn forbidden() -> (StatusCode, &'static str, String) { - ( - StatusCode::FORBIDDEN, - "text/text", - "403 Forbidden".to_string(), - ) -} -pub fn not_found() -> (StatusCode, &'static str, String) { - ( - StatusCode::NOT_FOUND, - "text/text", - "404 Not Found".to_string(), - ) +pub fn not_found() -> (StatusCode, String) { + (StatusCode::NOT_FOUND, "404 Not Found".to_string()) } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 5c3d29c..f96738f 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,7 +1,7 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::server::omikron_manager; use crate::server::short_link::add_short_link; -use crate::sql::connection_status::ConnectionType; +use crate::sql::connection_status::UserStatus; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; @@ -12,7 +12,6 @@ use dashmap::DashMap; use futures::SinkExt; use futures::stream::SplitSink; use futures::stream::SplitStream; -use futures::task::UnsafeFutureObj; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use json::JsonValue; @@ -138,7 +137,7 @@ impl OmikronConnection { let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { Some(k) => k, - None => { + _ => { self.send_error_response( &cv.get_id(), CommunicationType::error_invalid_public_key, @@ -226,7 +225,7 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id); + user_online_tracker::track_user_status(user_id, UserStatus::Online, omikron_id); } return; } @@ -235,7 +234,7 @@ impl OmikronConnection { if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( user_id, - ConnectionType::UserOffline, + UserStatus::UserOffline, status.omikron_id, ); } @@ -253,7 +252,7 @@ impl OmikronConnection { let _ = user_ids.push(JsonValue::from(user.0)); user_online_tracker::track_user_status( user.0, - ConnectionType::UserOffline, + UserStatus::UserOffline, omikron_id, ); } @@ -292,7 +291,7 @@ impl OmikronConnection { if let Some(user_id) = user_id_json.as_i64() { user_online_tracker::track_user_status( user_id, - ConnectionType::Online, + UserStatus::Online, omikron_id, ); } @@ -388,7 +387,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(ConnectionType::IotaOffline.to_string()), + JsonValue::String(UserStatus::IotaOffline.to_string()), ); } @@ -464,7 +463,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(ConnectionType::IotaOffline.to_string()), + JsonValue::String(UserStatus::IotaOffline.to_string()), ); } response = response.add_data( diff --git a/src/server/server.rs b/src/server/server.rs index d4790f3..fe88e3c 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -2,7 +2,6 @@ use crate::log; use crate::server::api; use crate::server::short_link::get_short_link; use crate::server::socket; -use crate::util::file_util::load_file_buf; use base64::Engine; use base64::engine::general_purpose::STANDARD; @@ -21,12 +20,14 @@ use rustls::ServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use sha1::{Digest, Sha1}; use std::error::Error; -use std::io::ErrorKind; -use std::io::{self, BufReader}; +use std::fs::{self, File}; +use std::io::{self, BufReader, ErrorKind}; use std::net::SocketAddr; +use std::path::{Path, PathBuf}; use std::result::Result::Ok; use std::sync::Arc; use std::{future::Future, pin::Pin, time::Duration}; + use tokio::net::TcpListener; use tokio::sync::broadcast; use tokio_rustls::TlsAcceptor; @@ -354,6 +355,36 @@ fn calculate_accept_key(key: &str) -> String { STANDARD.encode(result) } +pub fn load_file_buf(path: &str, name: &str) -> io::Result> { + let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); + let dir = exe + .parent() + .unwrap_or(Path::new(".")) + .to_string_lossy() + .to_string(); + let dir = Path::new(&dir).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + if let Err(_) = fs::create_dir_all(&dir) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Directory creation failed", + )); + } + } + + if !file_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "File creation failed", + )); + } + + let file = File::open(&file_path)?; + Ok(BufReader::new(file)) +} + /// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. fn load_tls_config() -> Result>, Box> { let cert_file_res = load_file_buf("certs", "cert.pem"); diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 5e7c68f..1c41491 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -1,38 +1,27 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ConnectionType { +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] +#[allow(unused)] +pub enum UserStatus { Online, - UserOffline, - IotaOffline, + PhoneOnline, Away, DoNotDisturb, + UserOffline, + IotaOffline, } -impl ConnectionType { - pub fn to_str(&self) -> &str { - match self { - ConnectionType::Online => "online", - ConnectionType::UserOffline => "user_offline", - ConnectionType::IotaOffline => "iota_offline", - ConnectionType::Away => "away", - ConnectionType::DoNotDisturb => "do_not_disturb", - } - } +#[allow(unused)] +impl UserStatus { pub fn to_string(&self) -> String { - match self { - ConnectionType::Online => "online".to_string(), - ConnectionType::UserOffline => "user_offline".to_string(), - ConnectionType::IotaOffline => "iota_offline".to_string(), - ConnectionType::Away => "away".to_string(), - ConnectionType::DoNotDisturb => "do_not_disturb".to_string(), - } + format!("{:?}", self) } - pub fn from_str(s: &str) -> Option { - match s.to_lowercase().as_str() { - "online" => Some(ConnectionType::Online), - "user_offline" => Some(ConnectionType::UserOffline), - "iota_offline" => Some(ConnectionType::IotaOffline), - "away" => Some(ConnectionType::Away), - "do_not_disturb" => Some(ConnectionType::DoNotDisturb), - _ => None, + pub fn from_str(s: &str) -> Option { + for sel in UserStatus::iter() { + if &sel.to_string() == s { + return Some(sel); + } } + None } } diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 20dd688..6a6ffc9 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -553,7 +553,7 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { id_u64 as i64, String::from_utf8_lossy(&public_key).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), }, Err(e) => Err(e), } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 3a1ab42..8cde17b 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,11 +1,11 @@ use crate::sql; -use crate::sql::connection_status::ConnectionType; +use crate::sql::connection_status::UserStatus; use dashmap::DashMap; use once_cell::sync::Lazy; #[derive(Debug, Clone)] -pub struct UserStatus { - pub connection_type: ConnectionType, +pub struct UserConnection { + pub connection_type: UserStatus, pub omikron_id: i64, } @@ -16,7 +16,7 @@ static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(Dash static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); // UserID -> UserStatus -static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); +static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { let mut entry = IOTA_OMIKRON_CONNECTIONS @@ -60,24 +60,20 @@ pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) } -pub fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { +pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { USER_STATUS_MAP.insert( user_id, - UserStatus { + UserConnection { connection_type: status, omikron_id, }, ); } -pub fn get_user_status(user_id: i64) -> Option { +pub fn get_user_status(user_id: i64) -> Option { USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } -pub fn untrack_user(user_id: i64) { - USER_STATUS_MAP.remove(&user_id); -} - pub fn untrack_many_users(user_ids: &[i64]) { for user_id in user_ids { USER_STATUS_MAP.remove(user_id); diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs index bb086b8..68e3855 100644 --- a/src/util/crypto_helper.rs +++ b/src/util/crypto_helper.rs @@ -10,31 +10,25 @@ use x448::{PublicKey, Secret, SharedSecret}; /// Errors for crypto operations #[derive(Debug)] pub enum CryptoError { - Base64Decode(base64::DecodeError), - InvalidKey, + Base64Decode, AgreementError, EncryptionError(aes_gcm::Error), DecryptionError(aes_gcm::Error), } impl From for CryptoError { - fn from(err: base64::DecodeError) -> Self { - CryptoError::Base64Decode(err) + fn from(_: base64::DecodeError) -> Self { + CryptoError::Base64Decode } } -pub struct KeyPair { - pub secret: Secret, - pub public: PublicKey, -} - -pub fn generate_keypair() -> KeyPair { +pub fn generate_keypair() -> (Secret, PublicKey) { let mut buf = [0u8; 56]; let mut rng = OsRng; rng.fill_bytes(&mut buf); let secret = Secret::from_bytes(&buf).unwrap(); let public = PublicKey::from(&secret); - KeyPair { secret, public } + (secret, public) } pub fn public_key_to_base64(pubkey: &PublicKey) -> String { diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs index 066149a..97acab4 100644 --- a/src/util/crypto_util.rs +++ b/src/util/crypto_util.rs @@ -5,7 +5,6 @@ use aes_gcm::{ use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; use hkdf::Hkdf; use sha2::{Digest, Sha256}; -use std::fmt; use x448::{PublicKey, Secret}; // --- Custom Errors --- diff --git a/src/util/file_util.rs b/src/util/file_util.rs deleted file mode 100644 index 383ba3b..0000000 --- a/src/util/file_util.rs +++ /dev/null @@ -1,350 +0,0 @@ -use crate::log; -use std::ffi::OsStr; -use std::fs::{self, File}; -use std::io::{self, BufReader, Read}; -use std::path::{Path, PathBuf}; -use sysinfo::System; -use uuid::Uuid; -use walkdir::WalkDir; -use zip::ZipArchive; - -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) { - log!( - "[IMPORTANT] Couldn't delete directory {}: {}", - directory.display(), - e - ); - return false; - } - true -} - -pub fn load_file_buf(path: &str, name: &str) -> io::Result> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - // Ensure the directory exists, create if necessary - if !dir.exists() { - if let Err(_) = fs::create_dir_all(&dir) { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "Directory creation failed", - )); - } - } - - // Create the file if it doesn't exist - if !file_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "File creation failed", - )); - } - - // Open the file and return a BufReader for efficient reading - let file = File::open(&file_path)?; - Ok(BufReader::new(file)) -} -pub fn has_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - if !dir.exists() { - return false; - } - - if !file_path.exists() { - return false; - } - - true -} -pub fn has_dir(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - - if !dir.exists() { - return false; - } - - true -} - -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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return String::new(); - } - return String::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!("[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 load_file_vec(path: &str, name: &str) -> Vec { - 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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return Vec::new(); - } - return Vec::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!("[IMPORTANT] Couldn't create file: {}", e); - } - return Vec::new(); - } - - let mut content = Vec::new(); - if let Ok(mut f) = File::open(&file_path) { - let _ = f.read_to_end(&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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return; - } - } - - if let Err(e) = fs::write(&file_path, value) { - log!( - "[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 used_dir_space(path: &str) -> u64 { - get_directory_size(&PathBuf::from(format!("{}/{}", get_directory(), path))) -} - -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)) -} - -// Helper to download the zip file content to a file on disk -async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { - let response = reqwest::get(url).await?; - - // Check for successful response status - if !response.status().is_success() { - return Err(format!("Failed to download file: Status {}", response.status()).into()); - } - - let mut zip_file = File::create(as_name)?; - let body = response.bytes().await?; - io::copy(&mut &*body, &mut zip_file)?; - - Ok(()) -} - -fn extract_zip_contents_to_folder( - zip_path: &Path, - target_dir: &Path, -) -> Result<(), Box> { - let file = File::open(zip_path)?; - let mut archive = ZipArchive::new(file)?; - - let staging_dir = target_dir.with_extension("staging"); - - let _ = fs::remove_dir_all(&staging_dir); - fs::create_dir_all(&staging_dir)?; - - let mut first_item_name: Option = None; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - let entry_path = staging_dir.join(file.sanitized_name()); - - if i == 0 { - if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { - first_item_name = Some(file.sanitized_name()); - } - } - - if file.name().ends_with('/') { - fs::create_dir_all(&entry_path)?; - } else { - if let Some(parent) = entry_path.parent() { - fs::create_dir_all(parent)?; - } - let mut out_file = File::create(entry_path)?; - io::copy(&mut file, &mut out_file)?; - } - } - - if let Some(root_path) = first_item_name { - let root_dir = staging_dir.join(&root_path); - - if root_dir.is_dir() { - let root_contents_count = fs::read_dir(&staging_dir)?.count(); - - if root_contents_count == 1 - || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) - { - let _ = fs::remove_dir_all(target_dir); - fs::create_dir_all(target_dir)?; - - for entry in fs::read_dir(root_dir)? { - let entry = entry?; - let src = entry.path(); - let dest = target_dir.join(entry.file_name()); - - if let Err(_) = fs::rename(&src, &dest) { - if src.is_file() { - fs::copy(&src, &dest)?; - } else { - if entry.path().is_dir() { - fs::rename(&src, &dest)?; - } - } - } - } - - let _ = fs::remove_dir_all(&staging_dir); - return Ok(()); - } - } - } - - log!("Extracting directly (no single root folder detected)."); - let _ = fs::remove_dir_all(target_dir); - fs::rename(&staging_dir, target_dir)?; - - Ok(()) -} - -pub async fn download_and_extract_zip(url: &str, as_name: &str) { - let base_dir = PathBuf::from(get_directory()); - let zip_filename = format!("{}.zip", Uuid::new_v4()); // Use a unique name for the downloaded ZIP file - let zip_path = base_dir.join(&zip_filename); - let target_dir = base_dir.join(as_name); - - // Step 1: Download the ZIP file - if let Err(e) = download_zip(url, &zip_path).await { - log!("Error downloading file: {}", e); - return; - } - - // Step 2: Extract and flatten the ZIP file contents into the target directory - if let Err(e) = extract_zip_contents_to_folder(&zip_path, &target_dir) { - log!("Error extracting ZIP file contents: {}", e); - } - - // Step 3: Clean up the downloaded ZIP file - if let Err(e) = fs::remove_file(&zip_path) { - log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index dede826..714ee53 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -12,6 +12,7 @@ use ansi_term::Color; static LOGGER: OnceLock> = OnceLock::new(); #[derive(Clone, Copy)] +#[allow(unused)] pub enum PrintType { Call, Client, @@ -56,7 +57,7 @@ pub fn startup() { let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); let sender = match msg.sender { Some(id) => fixed_box(&id.to_string(), 19), - None => fixed_box("", 19), + _ => fixed_box("", 19), }; let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); diff --git a/src/util/mod.rs b/src/util/mod.rs index 1897223..3472249 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,3 @@ pub mod crypto_helper; pub mod crypto_util; -pub mod file_util; pub mod logger; From e413a522691c85c4e13d0372b592062d263b86c5 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 29 Jan 2026 13:09:51 +0100 Subject: [PATCH 040/220] [Cleaned] --- src/server/api.rs | 58 +---- src/server/omikron_connection.rs | 17 +- src/server/server.rs | 37 +++- src/sql/connection_status.rs | 47 ++--- src/sql/sql.rs | 2 +- src/sql/user_online_tracker.rs | 18 +- src/util/crypto_helper.rs | 16 +- src/util/crypto_util.rs | 1 - src/util/file_util.rs | 350 ------------------------------- src/util/logger.rs | 3 +- src/util/mod.rs | 1 - 11 files changed, 86 insertions(+), 464 deletions(-) delete mode 100644 src/util/file_util.rs diff --git a/src/server/api.rs b/src/server/api.rs index a2e40ec..10be9fd 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -2,9 +2,7 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataType use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; -use crate::sql::user_online_tracker::{ - get_iota_omikron_connections, get_iota_primary_omikron_connection, -}; +use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, @@ -19,12 +17,12 @@ use json::number::Number; pub async fn handle( path: &str, - headers: HeaderMap, + _headers: HeaderMap, body_string: Option, ) -> HttpResponse> { let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); - let body: Option = if body_string.is_some() { + let _body: Option = if body_string.is_some() { if let Ok(body_json) = json::parse(&body_string.unwrap()) { Some(body_json) } else { @@ -37,7 +35,7 @@ pub async fn handle( // get/ // omikron/ // id/ - let (status, content, body_text) = if path_parts.len() >= 2 { + let (status, body_text) = if path_parts.len() >= 2 { match path_parts[1] { "get" => match path_parts[2] { // api/get/omikron -> any omikron @@ -50,7 +48,6 @@ pub async fn handle( { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", omikron_conn.get_omikron_id().await, @@ -61,14 +58,12 @@ pub async fn handle( } else { ( StatusCode::INTERNAL_SERVER_ERROR, - "text/plain", "selected an invalid omikron".to_string(), ) } } else { ( StatusCode::NOT_FOUND, - "text/plain", "couldn't find online omikron".to_string(), ) } @@ -79,7 +74,6 @@ pub async fn handle( } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", id, public_key, ip_address @@ -91,7 +85,6 @@ pub async fn handle( { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", omikron_id, public_key, ip_address @@ -109,7 +102,6 @@ pub async fn handle( { ( StatusCode::OK, - "application/json", format!( "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", omikron_id, public_key, ip_address @@ -171,11 +163,10 @@ pub async fn handle( DataTypes::sub_end, JsonValue::Number(Number::from(sub_end)), ); - (StatusCode::OK, "application/json", cv.to_json().to_string()) + (StatusCode::OK, cv.to_json().to_string()) } else { ( StatusCode::OK, - "application/json", CommunicationValue::new(CommunicationType::error_not_found) .to_json() .to_string(), @@ -184,11 +175,7 @@ pub async fn handle( } } } - "public_key" => ( - StatusCode::OK, - "application/json", - public_key_to_base64(&get_public_key()), - ), + "public_key" => (StatusCode::OK, public_key_to_base64(&get_public_key())), "user" => { if path_parts.len() != 4 { bad_request() @@ -247,11 +234,10 @@ pub async fn handle( base64::engine::general_purpose::STANDARD.encode(avatar), ); } - (StatusCode::OK, "application/json", cv.to_json().to_string()) + (StatusCode::OK, cv.to_json().to_string()) } else { ( StatusCode::OK, - "application/json", CommunicationValue::new(CommunicationType::error_not_found) .to_json() .to_string(), @@ -278,31 +264,9 @@ pub async fn handle( let body = Full::new(Bytes::from(body_text.to_string())); HttpResponse::builder().status(status).body(body).unwrap() } -pub fn bad_request() -> (StatusCode, &'static str, String) { - ( - StatusCode::BAD_REQUEST, - "text/text", - "400 Bad Request".to_string(), - ) +pub fn bad_request() -> (StatusCode, String) { + (StatusCode::BAD_REQUEST, "400 Bad Request".to_string()) } -pub fn unauthorized() -> (StatusCode, &'static str, String) { - ( - StatusCode::UNAUTHORIZED, - "text/text", - "401 Unauthorized".to_string(), - ) -} -pub fn forbidden() -> (StatusCode, &'static str, String) { - ( - StatusCode::FORBIDDEN, - "text/text", - "403 Forbidden".to_string(), - ) -} -pub fn not_found() -> (StatusCode, &'static str, String) { - ( - StatusCode::NOT_FOUND, - "text/text", - "404 Not Found".to_string(), - ) +pub fn not_found() -> (StatusCode, String) { + (StatusCode::NOT_FOUND, "404 Not Found".to_string()) } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 5c3d29c..f96738f 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,7 +1,7 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::server::omikron_manager; use crate::server::short_link::add_short_link; -use crate::sql::connection_status::ConnectionType; +use crate::sql::connection_status::UserStatus; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; @@ -12,7 +12,6 @@ use dashmap::DashMap; use futures::SinkExt; use futures::stream::SplitSink; use futures::stream::SplitStream; -use futures::task::UnsafeFutureObj; use hyper::upgrade::Upgraded; use hyper_util::rt::TokioIo; use json::JsonValue; @@ -138,7 +137,7 @@ impl OmikronConnection { let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { Some(k) => k, - None => { + _ => { self.send_error_response( &cv.get_id(), CommunicationType::error_invalid_public_key, @@ -226,7 +225,7 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, ConnectionType::Online, omikron_id); + user_online_tracker::track_user_status(user_id, UserStatus::Online, omikron_id); } return; } @@ -235,7 +234,7 @@ impl OmikronConnection { if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( user_id, - ConnectionType::UserOffline, + UserStatus::UserOffline, status.omikron_id, ); } @@ -253,7 +252,7 @@ impl OmikronConnection { let _ = user_ids.push(JsonValue::from(user.0)); user_online_tracker::track_user_status( user.0, - ConnectionType::UserOffline, + UserStatus::UserOffline, omikron_id, ); } @@ -292,7 +291,7 @@ impl OmikronConnection { if let Some(user_id) = user_id_json.as_i64() { user_online_tracker::track_user_status( user_id, - ConnectionType::Online, + UserStatus::Online, omikron_id, ); } @@ -388,7 +387,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(ConnectionType::IotaOffline.to_string()), + JsonValue::String(UserStatus::IotaOffline.to_string()), ); } @@ -464,7 +463,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(ConnectionType::IotaOffline.to_string()), + JsonValue::String(UserStatus::IotaOffline.to_string()), ); } response = response.add_data( diff --git a/src/server/server.rs b/src/server/server.rs index d4790f3..fe88e3c 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -2,7 +2,6 @@ use crate::log; use crate::server::api; use crate::server::short_link::get_short_link; use crate::server::socket; -use crate::util::file_util::load_file_buf; use base64::Engine; use base64::engine::general_purpose::STANDARD; @@ -21,12 +20,14 @@ use rustls::ServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use sha1::{Digest, Sha1}; use std::error::Error; -use std::io::ErrorKind; -use std::io::{self, BufReader}; +use std::fs::{self, File}; +use std::io::{self, BufReader, ErrorKind}; use std::net::SocketAddr; +use std::path::{Path, PathBuf}; use std::result::Result::Ok; use std::sync::Arc; use std::{future::Future, pin::Pin, time::Duration}; + use tokio::net::TcpListener; use tokio::sync::broadcast; use tokio_rustls::TlsAcceptor; @@ -354,6 +355,36 @@ fn calculate_accept_key(key: &str) -> String { STANDARD.encode(result) } +pub fn load_file_buf(path: &str, name: &str) -> io::Result> { + let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); + let dir = exe + .parent() + .unwrap_or(Path::new(".")) + .to_string_lossy() + .to_string(); + let dir = Path::new(&dir).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + if let Err(_) = fs::create_dir_all(&dir) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Directory creation failed", + )); + } + } + + if !file_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "File creation failed", + )); + } + + let file = File::open(&file_path)?; + Ok(BufReader::new(file)) +} + /// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. fn load_tls_config() -> Result>, Box> { let cert_file_res = load_file_buf("certs", "cert.pem"); diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 5e7c68f..1c41491 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -1,38 +1,27 @@ -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum ConnectionType { +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] +#[allow(unused)] +pub enum UserStatus { Online, - UserOffline, - IotaOffline, + PhoneOnline, Away, DoNotDisturb, + UserOffline, + IotaOffline, } -impl ConnectionType { - pub fn to_str(&self) -> &str { - match self { - ConnectionType::Online => "online", - ConnectionType::UserOffline => "user_offline", - ConnectionType::IotaOffline => "iota_offline", - ConnectionType::Away => "away", - ConnectionType::DoNotDisturb => "do_not_disturb", - } - } +#[allow(unused)] +impl UserStatus { pub fn to_string(&self) -> String { - match self { - ConnectionType::Online => "online".to_string(), - ConnectionType::UserOffline => "user_offline".to_string(), - ConnectionType::IotaOffline => "iota_offline".to_string(), - ConnectionType::Away => "away".to_string(), - ConnectionType::DoNotDisturb => "do_not_disturb".to_string(), - } + format!("{:?}", self) } - pub fn from_str(s: &str) -> Option { - match s.to_lowercase().as_str() { - "online" => Some(ConnectionType::Online), - "user_offline" => Some(ConnectionType::UserOffline), - "iota_offline" => Some(ConnectionType::IotaOffline), - "away" => Some(ConnectionType::Away), - "do_not_disturb" => Some(ConnectionType::DoNotDisturb), - _ => None, + pub fn from_str(s: &str) -> Option { + for sel in UserStatus::iter() { + if &sel.to_string() == s { + return Some(sel); + } } + None } } diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 20dd688..6a6ffc9 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -553,7 +553,7 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { id_u64 as i64, String::from_utf8_lossy(&public_key).to_string(), )), - None => Err(sqlx::Error::RowNotFound), + _ => Err(sqlx::Error::RowNotFound), }, Err(e) => Err(e), } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 3a1ab42..8cde17b 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,11 +1,11 @@ use crate::sql; -use crate::sql::connection_status::ConnectionType; +use crate::sql::connection_status::UserStatus; use dashmap::DashMap; use once_cell::sync::Lazy; #[derive(Debug, Clone)] -pub struct UserStatus { - pub connection_type: ConnectionType, +pub struct UserConnection { + pub connection_type: UserStatus, pub omikron_id: i64, } @@ -16,7 +16,7 @@ static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(Dash static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); // UserID -> UserStatus -static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); +static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { let mut entry = IOTA_OMIKRON_CONNECTIONS @@ -60,24 +60,20 @@ pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) } -pub fn track_user_status(user_id: i64, status: ConnectionType, omikron_id: i64) { +pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { USER_STATUS_MAP.insert( user_id, - UserStatus { + UserConnection { connection_type: status, omikron_id, }, ); } -pub fn get_user_status(user_id: i64) -> Option { +pub fn get_user_status(user_id: i64) -> Option { USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } -pub fn untrack_user(user_id: i64) { - USER_STATUS_MAP.remove(&user_id); -} - pub fn untrack_many_users(user_ids: &[i64]) { for user_id in user_ids { USER_STATUS_MAP.remove(user_id); diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs index bb086b8..68e3855 100644 --- a/src/util/crypto_helper.rs +++ b/src/util/crypto_helper.rs @@ -10,31 +10,25 @@ use x448::{PublicKey, Secret, SharedSecret}; /// Errors for crypto operations #[derive(Debug)] pub enum CryptoError { - Base64Decode(base64::DecodeError), - InvalidKey, + Base64Decode, AgreementError, EncryptionError(aes_gcm::Error), DecryptionError(aes_gcm::Error), } impl From for CryptoError { - fn from(err: base64::DecodeError) -> Self { - CryptoError::Base64Decode(err) + fn from(_: base64::DecodeError) -> Self { + CryptoError::Base64Decode } } -pub struct KeyPair { - pub secret: Secret, - pub public: PublicKey, -} - -pub fn generate_keypair() -> KeyPair { +pub fn generate_keypair() -> (Secret, PublicKey) { let mut buf = [0u8; 56]; let mut rng = OsRng; rng.fill_bytes(&mut buf); let secret = Secret::from_bytes(&buf).unwrap(); let public = PublicKey::from(&secret); - KeyPair { secret, public } + (secret, public) } pub fn public_key_to_base64(pubkey: &PublicKey) -> String { diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs index 066149a..97acab4 100644 --- a/src/util/crypto_util.rs +++ b/src/util/crypto_util.rs @@ -5,7 +5,6 @@ use aes_gcm::{ use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; use hkdf::Hkdf; use sha2::{Digest, Sha256}; -use std::fmt; use x448::{PublicKey, Secret}; // --- Custom Errors --- diff --git a/src/util/file_util.rs b/src/util/file_util.rs deleted file mode 100644 index 383ba3b..0000000 --- a/src/util/file_util.rs +++ /dev/null @@ -1,350 +0,0 @@ -use crate::log; -use std::ffi::OsStr; -use std::fs::{self, File}; -use std::io::{self, BufReader, Read}; -use std::path::{Path, PathBuf}; -use sysinfo::System; -use uuid::Uuid; -use walkdir::WalkDir; -use zip::ZipArchive; - -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) { - log!( - "[IMPORTANT] Couldn't delete directory {}: {}", - directory.display(), - e - ); - return false; - } - true -} - -pub fn load_file_buf(path: &str, name: &str) -> io::Result> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - // Ensure the directory exists, create if necessary - if !dir.exists() { - if let Err(_) = fs::create_dir_all(&dir) { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "Directory creation failed", - )); - } - } - - // Create the file if it doesn't exist - if !file_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "File creation failed", - )); - } - - // Open the file and return a BufReader for efficient reading - let file = File::open(&file_path)?; - Ok(BufReader::new(file)) -} -pub fn has_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - if !dir.exists() { - return false; - } - - if !file_path.exists() { - return false; - } - - true -} -pub fn has_dir(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - - if !dir.exists() { - return false; - } - - true -} - -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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return String::new(); - } - return String::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!("[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 load_file_vec(path: &str, name: &str) -> Vec { - 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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return Vec::new(); - } - return Vec::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!("[IMPORTANT] Couldn't create file: {}", e); - } - return Vec::new(); - } - - let mut content = Vec::new(); - if let Ok(mut f) = File::open(&file_path) { - let _ = f.read_to_end(&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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return; - } - } - - if let Err(e) = fs::write(&file_path, value) { - log!( - "[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 used_dir_space(path: &str) -> u64 { - get_directory_size(&PathBuf::from(format!("{}/{}", get_directory(), path))) -} - -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)) -} - -// Helper to download the zip file content to a file on disk -async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { - let response = reqwest::get(url).await?; - - // Check for successful response status - if !response.status().is_success() { - return Err(format!("Failed to download file: Status {}", response.status()).into()); - } - - let mut zip_file = File::create(as_name)?; - let body = response.bytes().await?; - io::copy(&mut &*body, &mut zip_file)?; - - Ok(()) -} - -fn extract_zip_contents_to_folder( - zip_path: &Path, - target_dir: &Path, -) -> Result<(), Box> { - let file = File::open(zip_path)?; - let mut archive = ZipArchive::new(file)?; - - let staging_dir = target_dir.with_extension("staging"); - - let _ = fs::remove_dir_all(&staging_dir); - fs::create_dir_all(&staging_dir)?; - - let mut first_item_name: Option = None; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - let entry_path = staging_dir.join(file.sanitized_name()); - - if i == 0 { - if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { - first_item_name = Some(file.sanitized_name()); - } - } - - if file.name().ends_with('/') { - fs::create_dir_all(&entry_path)?; - } else { - if let Some(parent) = entry_path.parent() { - fs::create_dir_all(parent)?; - } - let mut out_file = File::create(entry_path)?; - io::copy(&mut file, &mut out_file)?; - } - } - - if let Some(root_path) = first_item_name { - let root_dir = staging_dir.join(&root_path); - - if root_dir.is_dir() { - let root_contents_count = fs::read_dir(&staging_dir)?.count(); - - if root_contents_count == 1 - || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) - { - let _ = fs::remove_dir_all(target_dir); - fs::create_dir_all(target_dir)?; - - for entry in fs::read_dir(root_dir)? { - let entry = entry?; - let src = entry.path(); - let dest = target_dir.join(entry.file_name()); - - if let Err(_) = fs::rename(&src, &dest) { - if src.is_file() { - fs::copy(&src, &dest)?; - } else { - if entry.path().is_dir() { - fs::rename(&src, &dest)?; - } - } - } - } - - let _ = fs::remove_dir_all(&staging_dir); - return Ok(()); - } - } - } - - log!("Extracting directly (no single root folder detected)."); - let _ = fs::remove_dir_all(target_dir); - fs::rename(&staging_dir, target_dir)?; - - Ok(()) -} - -pub async fn download_and_extract_zip(url: &str, as_name: &str) { - let base_dir = PathBuf::from(get_directory()); - let zip_filename = format!("{}.zip", Uuid::new_v4()); // Use a unique name for the downloaded ZIP file - let zip_path = base_dir.join(&zip_filename); - let target_dir = base_dir.join(as_name); - - // Step 1: Download the ZIP file - if let Err(e) = download_zip(url, &zip_path).await { - log!("Error downloading file: {}", e); - return; - } - - // Step 2: Extract and flatten the ZIP file contents into the target directory - if let Err(e) = extract_zip_contents_to_folder(&zip_path, &target_dir) { - log!("Error extracting ZIP file contents: {}", e); - } - - // Step 3: Clean up the downloaded ZIP file - if let Err(e) = fs::remove_file(&zip_path) { - log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index dede826..714ee53 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -12,6 +12,7 @@ use ansi_term::Color; static LOGGER: OnceLock> = OnceLock::new(); #[derive(Clone, Copy)] +#[allow(unused)] pub enum PrintType { Call, Client, @@ -56,7 +57,7 @@ pub fn startup() { let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); let sender = match msg.sender { Some(id) => fixed_box(&id.to_string(), 19), - None => fixed_box("", 19), + _ => fixed_box("", 19), }; let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); diff --git a/src/util/mod.rs b/src/util/mod.rs index 1897223..3472249 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,3 @@ pub mod crypto_helper; pub mod crypto_util; -pub mod file_util; pub mod logger; From 25b927208d3405c503b1b0a23df312ec4cf66ab4 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Thu, 29 Jan 2026 13:50:58 +0100 Subject: [PATCH 041/220] [Fix] user status names --- src/server/omikron_connection.rs | 12 ++++++------ src/sql/connection_status.rs | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f96738f..07fde6c 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -225,7 +225,7 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, UserStatus::Online, omikron_id); + user_online_tracker::track_user_status(user_id, UserStatus::online, omikron_id); } return; } @@ -234,7 +234,7 @@ impl OmikronConnection { if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( user_id, - UserStatus::UserOffline, + UserStatus::user_offline, status.omikron_id, ); } @@ -252,7 +252,7 @@ impl OmikronConnection { let _ = user_ids.push(JsonValue::from(user.0)); user_online_tracker::track_user_status( user.0, - UserStatus::UserOffline, + UserStatus::user_offline, omikron_id, ); } @@ -291,7 +291,7 @@ impl OmikronConnection { if let Some(user_id) = user_id_json.as_i64() { user_online_tracker::track_user_status( user_id, - UserStatus::Online, + UserStatus::online, omikron_id, ); } @@ -387,7 +387,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(UserStatus::IotaOffline.to_string()), + JsonValue::String(UserStatus::iota_offline.to_string()), ); } @@ -463,7 +463,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(UserStatus::IotaOffline.to_string()), + JsonValue::String(UserStatus::iota_offline.to_string()), ); } response = response.add_data( diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 1c41491..d5b8603 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -2,14 +2,14 @@ use strum::IntoEnumIterator; use strum_macros::EnumIter; #[derive(Debug, Clone, PartialEq, EnumIter, Eq)] -#[allow(unused)] +#[allow(unused, non_camel_case_types)] pub enum UserStatus { - Online, - PhoneOnline, - Away, - DoNotDisturb, - UserOffline, - IotaOffline, + online, + phone_online, + away, + do_not_disturb, + user_offline, + iota_offline, } #[allow(unused)] impl UserStatus { From 55bfceffb1b4323c1edca560c4199b2dc73512ef Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Thu, 29 Jan 2026 13:50:58 +0100 Subject: [PATCH 042/220] [Fix] user status names --- src/server/omikron_connection.rs | 12 ++++++------ src/sql/connection_status.rs | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f96738f..07fde6c 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -225,7 +225,7 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, UserStatus::Online, omikron_id); + user_online_tracker::track_user_status(user_id, UserStatus::online, omikron_id); } return; } @@ -234,7 +234,7 @@ impl OmikronConnection { if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( user_id, - UserStatus::UserOffline, + UserStatus::user_offline, status.omikron_id, ); } @@ -252,7 +252,7 @@ impl OmikronConnection { let _ = user_ids.push(JsonValue::from(user.0)); user_online_tracker::track_user_status( user.0, - UserStatus::UserOffline, + UserStatus::user_offline, omikron_id, ); } @@ -291,7 +291,7 @@ impl OmikronConnection { if let Some(user_id) = user_id_json.as_i64() { user_online_tracker::track_user_status( user_id, - UserStatus::Online, + UserStatus::online, omikron_id, ); } @@ -387,7 +387,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(UserStatus::IotaOffline.to_string()), + JsonValue::String(UserStatus::iota_offline.to_string()), ); } @@ -463,7 +463,7 @@ impl OmikronConnection { } else { response = response.add_data( DataTypes::online_status, - JsonValue::String(UserStatus::IotaOffline.to_string()), + JsonValue::String(UserStatus::iota_offline.to_string()), ); } response = response.add_data( diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 1c41491..d5b8603 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -2,14 +2,14 @@ use strum::IntoEnumIterator; use strum_macros::EnumIter; #[derive(Debug, Clone, PartialEq, EnumIter, Eq)] -#[allow(unused)] +#[allow(unused, non_camel_case_types)] pub enum UserStatus { - Online, - PhoneOnline, - Away, - DoNotDisturb, - UserOffline, - IotaOffline, + online, + phone_online, + away, + do_not_disturb, + user_offline, + iota_offline, } #[allow(unused)] impl UserStatus { From 98b9a6fbdfff77be66e3bd245c79a59a3a14934b Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 3 Feb 2026 13:15:19 +0100 Subject: [PATCH 043/220] [FIX] Performance --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index bf945e4..07b461c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,5 +45,5 @@ async fn main() { } server::server::start(9187).await; log!(" Server"); - loop {} + tokio::signal::ctrl_c().await.unwrap(); } From e69c1cbbb7367560d6c0a55a662a2bd2107cb12d Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Tue, 3 Feb 2026 13:15:19 +0100 Subject: [PATCH 044/220] [FIX] Performance --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index bf945e4..07b461c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,5 +45,5 @@ async fn main() { } server::server::start(9187).await; log!(" Server"); - loop {} + tokio::signal::ctrl_c().await.unwrap(); } From 5e4f6333fdd474a6adcde333aa0771b8083d0e96 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Fri, 6 Feb 2026 19:44:05 +0100 Subject: [PATCH 045/220] [Add] Logging Omikron Connections --- src/server/omikron_connection.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 07fde6c..e8edc41 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -191,6 +191,7 @@ impl OmikronConnection { .add_data(DataTypes::accepted, JsonValue::Boolean(true)), ) .await; + log_in!(PrintType::Omega, "Omikron Connected"); } else { self.send_error_response( &cv.get_id(), @@ -897,6 +898,7 @@ impl OmikronConnection { if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { + log_in!(PrintType::Omega, "Omikron Disconnected"); omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } @@ -907,6 +909,7 @@ impl OmikronConnection { if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { + log_in!(PrintType::Omega, "Omikron Disconnected"); omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } From 265f720370ef0d4be4d4e34b03644a40eac26c46 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Fri, 6 Feb 2026 19:44:05 +0100 Subject: [PATCH 046/220] [Add] Logging Omikron Connections --- src/server/omikron_connection.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 07fde6c..e8edc41 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -191,6 +191,7 @@ impl OmikronConnection { .add_data(DataTypes::accepted, JsonValue::Boolean(true)), ) .await; + log_in!(PrintType::Omega, "Omikron Connected"); } else { self.send_error_response( &cv.get_id(), @@ -897,6 +898,7 @@ impl OmikronConnection { if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { + log_in!(PrintType::Omega, "Omikron Disconnected"); omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } @@ -907,6 +909,7 @@ impl OmikronConnection { if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { + log_in!(PrintType::Omega, "Omikron Disconnected"); omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } From 10ea9b4ae4dbdf8ebe5110e7370f944b7b11ae71 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Sun, 8 Feb 2026 20:08:26 +0100 Subject: [PATCH 047/220] [Add] Notification Logic --- src/data/communication.rs | 6 +++ src/server/omikron_connection.rs | 55 ++++++++++++++++++++ src/sql/sql.rs | 87 +++++++++++++++++++++++++++----- 3 files changed, 135 insertions(+), 13 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 7c1660d..1ac5857 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -35,6 +35,7 @@ pub enum DataTypes { denied_profiles, content, messages, + notifications, send_time, get_time, get_variant, @@ -139,6 +140,11 @@ pub enum CommunicationType { message_other_iota, message_chunk, messages_get, + + push_notification, + read_notification, + get_notifications, + change_confirm, confirm_receive, confirm_read, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e8edc41..7ce8f8b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -887,6 +887,61 @@ impl OmikronConnection { } return; } + + // NOTIFICATIONS + if cv.is_type(CommunicationType::get_notifications) { + if let Some(JsonValue::Number(user_id)) = cv.get_data(DataTypes::user_id) { + if let Ok(notifications) = + sql::get_notifications(user_id.as_fixed_point_i64(0).unwrap()).await + { + let mut json_array = Vec::new(); + for (sender, amount) in notifications { + let mut obj = JsonValue::new_object(); + let _ = obj.insert("sender", JsonValue::from(sender)); + let _ = obj.insert("amount", JsonValue::from(amount)); + json_array.push(obj); + } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_array(DataTypes::notifications, json_array); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::read_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::read_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::push_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::add_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 6a6ffc9..bb79893 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -94,7 +94,17 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { ) .execute(&pool) .await; - + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + notifications ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, + sender_id BIGINT UNSIGNED NOT NULL, + receiver_id BIGINT UNSIGNED NOT NULL, + amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + )", + ) + .execute(&pool) + .await; *db_lock = Some(pool); Ok(()) } @@ -396,6 +406,17 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro Ok(()) } +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -524,18 +545,6 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s Ok(()) } -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(pool) - .await?; - - Ok(()) -} - pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -607,3 +616,55 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> _ => Err(sqlx::Error::RowNotFound), } } + +// ========================================================================================== +// PHI +// ========================================================================================== + +pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + INSERT INTO notifications (sender_id, receiver_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64)>( + r#" + SELECT sender_id, amount FROM notifications WHERE receiver_id = ? + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await +} From 0172d1881ece0ff636caa5428673f1b5b09c6388 Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Sun, 8 Feb 2026 20:08:26 +0100 Subject: [PATCH 048/220] [Add] Notification Logic --- src/data/communication.rs | 6 +++ src/server/omikron_connection.rs | 55 ++++++++++++++++++++ src/sql/sql.rs | 87 +++++++++++++++++++++++++++----- 3 files changed, 135 insertions(+), 13 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 7c1660d..1ac5857 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -35,6 +35,7 @@ pub enum DataTypes { denied_profiles, content, messages, + notifications, send_time, get_time, get_variant, @@ -139,6 +140,11 @@ pub enum CommunicationType { message_other_iota, message_chunk, messages_get, + + push_notification, + read_notification, + get_notifications, + change_confirm, confirm_receive, confirm_read, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e8edc41..7ce8f8b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -887,6 +887,61 @@ impl OmikronConnection { } return; } + + // NOTIFICATIONS + if cv.is_type(CommunicationType::get_notifications) { + if let Some(JsonValue::Number(user_id)) = cv.get_data(DataTypes::user_id) { + if let Ok(notifications) = + sql::get_notifications(user_id.as_fixed_point_i64(0).unwrap()).await + { + let mut json_array = Vec::new(); + for (sender, amount) in notifications { + let mut obj = JsonValue::new_object(); + let _ = obj.insert("sender", JsonValue::from(sender)); + let _ = obj.insert("amount", JsonValue::from(amount)); + json_array.push(obj); + } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_array(DataTypes::notifications, json_array); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::read_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::read_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::push_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::add_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 6a6ffc9..bb79893 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -94,7 +94,17 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { ) .execute(&pool) .await; - + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + notifications ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, + sender_id BIGINT UNSIGNED NOT NULL, + receiver_id BIGINT UNSIGNED NOT NULL, + amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + )", + ) + .execute(&pool) + .await; *db_lock = Some(pool); Ok(()) } @@ -396,6 +406,17 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro Ok(()) } +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -524,18 +545,6 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s Ok(()) } -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(pool) - .await?; - - Ok(()) -} - pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -607,3 +616,55 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> _ => Err(sqlx::Error::RowNotFound), } } + +// ========================================================================================== +// PHI +// ========================================================================================== + +pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + INSERT INTO notifications (sender_id, receiver_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64)>( + r#" + SELECT sender_id, amount FROM notifications WHERE receiver_id = ? + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await +} From c4bc26935b6cc6b2e9d22e123340cb2a2d08ff0e Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Sun, 8 Feb 2026 20:08:26 +0100 Subject: [PATCH 049/220] [Add] Notification Logic --- src/data/communication.rs | 9 ++++ src/server/omikron_connection.rs | 55 ++++++++++++++++++++ src/sql/sql.rs | 87 +++++++++++++++++++++++++++----- 3 files changed, 138 insertions(+), 13 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 7c1660d..99229f5 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -35,6 +35,7 @@ pub enum DataTypes { denied_profiles, content, messages, + notifications, send_time, get_time, get_variant, @@ -53,6 +54,7 @@ pub enum DataTypes { signature, signed, message, + message_state, last_ping, ping_iota, ping_clients, @@ -112,6 +114,7 @@ impl DataTypes { #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, + error_anonymous, error_internal, error_invalid_data, error_invalid_user_id, @@ -134,11 +137,17 @@ pub enum CommunicationType { settings_load, settings_list, message, + message_state, message_send, message_live, message_other_iota, message_chunk, messages_get, + + push_notification, + read_notification, + get_notifications, + change_confirm, confirm_receive, confirm_read, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e8edc41..7ce8f8b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -887,6 +887,61 @@ impl OmikronConnection { } return; } + + // NOTIFICATIONS + if cv.is_type(CommunicationType::get_notifications) { + if let Some(JsonValue::Number(user_id)) = cv.get_data(DataTypes::user_id) { + if let Ok(notifications) = + sql::get_notifications(user_id.as_fixed_point_i64(0).unwrap()).await + { + let mut json_array = Vec::new(); + for (sender, amount) in notifications { + let mut obj = JsonValue::new_object(); + let _ = obj.insert("sender", JsonValue::from(sender)); + let _ = obj.insert("amount", JsonValue::from(amount)); + json_array.push(obj); + } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_array(DataTypes::notifications, json_array); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::read_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::read_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::push_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::add_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 6a6ffc9..bb79893 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -94,7 +94,17 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { ) .execute(&pool) .await; - + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + notifications ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, + sender_id BIGINT UNSIGNED NOT NULL, + receiver_id BIGINT UNSIGNED NOT NULL, + amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + )", + ) + .execute(&pool) + .await; *db_lock = Some(pool); Ok(()) } @@ -396,6 +406,17 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro Ok(()) } +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -524,18 +545,6 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s Ok(()) } -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(pool) - .await?; - - Ok(()) -} - pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -607,3 +616,55 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> _ => Err(sqlx::Error::RowNotFound), } } + +// ========================================================================================== +// PHI +// ========================================================================================== + +pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + INSERT INTO notifications (sender_id, receiver_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64)>( + r#" + SELECT sender_id, amount FROM notifications WHERE receiver_id = ? + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await +} From 7494b7b8850fad8ee1883acc515c471625e861fd Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Sun, 8 Feb 2026 20:08:26 +0100 Subject: [PATCH 050/220] [Add] Notification Logic --- src/data/communication.rs | 9 ++++ src/server/omikron_connection.rs | 55 ++++++++++++++++++++ src/sql/sql.rs | 87 +++++++++++++++++++++++++++----- 3 files changed, 138 insertions(+), 13 deletions(-) diff --git a/src/data/communication.rs b/src/data/communication.rs index 7c1660d..99229f5 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -35,6 +35,7 @@ pub enum DataTypes { denied_profiles, content, messages, + notifications, send_time, get_time, get_variant, @@ -53,6 +54,7 @@ pub enum DataTypes { signature, signed, message, + message_state, last_ping, ping_iota, ping_clients, @@ -112,6 +114,7 @@ impl DataTypes { #[allow(non_camel_case_types, dead_code)] pub enum CommunicationType { error, + error_anonymous, error_internal, error_invalid_data, error_invalid_user_id, @@ -134,11 +137,17 @@ pub enum CommunicationType { settings_load, settings_list, message, + message_state, message_send, message_live, message_other_iota, message_chunk, messages_get, + + push_notification, + read_notification, + get_notifications, + change_confirm, confirm_receive, confirm_read, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e8edc41..7ce8f8b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -887,6 +887,61 @@ impl OmikronConnection { } return; } + + // NOTIFICATIONS + if cv.is_type(CommunicationType::get_notifications) { + if let Some(JsonValue::Number(user_id)) = cv.get_data(DataTypes::user_id) { + if let Ok(notifications) = + sql::get_notifications(user_id.as_fixed_point_i64(0).unwrap()).await + { + let mut json_array = Vec::new(); + for (sender, amount) in notifications { + let mut obj = JsonValue::new_object(); + let _ = obj.insert("sender", JsonValue::from(sender)); + let _ = obj.insert("amount", JsonValue::from(amount)); + json_array.push(obj); + } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_array(DataTypes::notifications, json_array); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::read_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::read_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } + if cv.is_type(CommunicationType::push_notification) { + if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( + cv.get_data(DataTypes::receiver_id), + cv.get_data(DataTypes::sender_id), + ) { + if let Ok(_) = sql::add_notification( + user_id.as_fixed_point_i64(0).unwrap(), + other_id.as_fixed_point_i64(0).unwrap(), + ) + .await + { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send_message(&response).await; + } + } + } } async fn send_error_response(&self, message_id: &Uuid, error_type: CommunicationType) { diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 6a6ffc9..bb79893 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -94,7 +94,17 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { ) .execute(&pool) .await; - + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + notifications ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, + sender_id BIGINT UNSIGNED NOT NULL, + receiver_id BIGINT UNSIGNED NOT NULL, + amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + )", + ) + .execute(&pool) + .await; *db_lock = Some(pool); Ok(()) } @@ -396,6 +406,17 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro Ok(()) } +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(pool) + .await?; + + Ok(()) +} pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -524,18 +545,6 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s Ok(()) } -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(pool) - .await?; - - Ok(()) -} - pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { let db_lock = SQL_DB.read().await; let pool = db_lock.as_ref().expect("Database pool is not initialized"); @@ -607,3 +616,55 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> _ => Err(sqlx::Error::RowNotFound), } } + +// ========================================================================================== +// PHI +// ========================================================================================== + +pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + INSERT INTO notifications (sender_id, receiver_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64)>( + r#" + SELECT sender_id, amount FROM notifications WHERE receiver_id = ? + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await +} From fd33ce58790541f8bf72b174e9b6a1c5b1e8a1d5 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:24:14 +0100 Subject: [PATCH 051/220] [Add] AI Keep alive --- src/server/socket.rs | 61 +++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/server/socket.rs b/src/server/socket.rs index 6b48154..c9191ea 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -40,30 +40,55 @@ pub fn handle(path: String, upgrades: OnUpgrade) { }); } pub async fn start_connecteable_handler(connection: Arc) { + use futures::SinkExt; + use tokio::time::Duration; + + const IDLE_TIMEOUT: Duration = Duration::from_secs(30); + loop { - let msg = match { - let mut receiver = connection.receiver.write().await; - receiver.next().await - } { - Some(Ok(msg)) => msg, - Some(Err(e)) => { + let mut receiver = connection.receiver.write().await; + + match tokio::time::timeout(IDLE_TIMEOUT, receiver.next()).await { + Ok(Some(Ok(msg))) => { + // Drop the lock so other tasks can use the receiver if needed, + // and so we can handle the message without holding the lock. + drop(receiver); + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + Message::Pong(_) => { + // Received a pong, connection is alive. + } + _ => {} + } + } + Ok(Some(Err(e))) => { log!("WS Error: {}", e); break; } - _ => break, - }; - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Close(_) => { + Ok(None) => { + // Stream is closed break; } - _ => {} + Err(_) => { + // Timeout, we need to send a ping. + // Drop receiver lock before acquiring sender lock to avoid deadlock. + drop(receiver); + log!("WebSocket connection is idle. Sending a ping."); + let mut sender = connection.sender.write().await; + if let Err(e) = sender.send(Message::Ping(vec![])).await { + log!("Failed to send ping: {}. Closing connection.", e); + break; + } + } } } connection.handle_close().await; From d37d8523273f91616385e88a3c1ca66b5c66a83a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:24:14 +0100 Subject: [PATCH 052/220] [Add] AI Keep alive --- src/server/socket.rs | 61 +++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/server/socket.rs b/src/server/socket.rs index 6b48154..c9191ea 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -40,30 +40,55 @@ pub fn handle(path: String, upgrades: OnUpgrade) { }); } pub async fn start_connecteable_handler(connection: Arc) { + use futures::SinkExt; + use tokio::time::Duration; + + const IDLE_TIMEOUT: Duration = Duration::from_secs(30); + loop { - let msg = match { - let mut receiver = connection.receiver.write().await; - receiver.next().await - } { - Some(Ok(msg)) => msg, - Some(Err(e)) => { + let mut receiver = connection.receiver.write().await; + + match tokio::time::timeout(IDLE_TIMEOUT, receiver.next()).await { + Ok(Some(Ok(msg))) => { + // Drop the lock so other tasks can use the receiver if needed, + // and so we can handle the message without holding the lock. + drop(receiver); + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + Message::Pong(_) => { + // Received a pong, connection is alive. + } + _ => {} + } + } + Ok(Some(Err(e))) => { log!("WS Error: {}", e); break; } - _ => break, - }; - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Close(_) => { + Ok(None) => { + // Stream is closed break; } - _ => {} + Err(_) => { + // Timeout, we need to send a ping. + // Drop receiver lock before acquiring sender lock to avoid deadlock. + drop(receiver); + log!("WebSocket connection is idle. Sending a ping."); + let mut sender = connection.sender.write().await; + if let Err(e) = sender.send(Message::Ping(vec![])).await { + log!("Failed to send ping: {}. Closing connection.", e); + break; + } + } } } connection.handle_close().await; From 89ea0b98399050340ebfacd940fb1e7b712065e9 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:24:14 +0100 Subject: [PATCH 053/220] [Add] AI Keep alive + Human Bugfix --- src/server/socket.rs | 71 ++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/src/server/socket.rs b/src/server/socket.rs index 6b48154..f18f525 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -4,8 +4,9 @@ use futures::StreamExt; use hyper::upgrade::OnUpgrade; use hyper_util::rt::TokioIo; use tokio_tungstenite::WebSocketStream; -use tungstenite::Message; +use tungstenite::{Message, Utf8Bytes}; +use crate::data::communication::{CommunicationType, CommunicationValue}; use crate::log; use crate::server::omikron_connection::OmikronConnection; @@ -40,30 +41,62 @@ pub fn handle(path: String, upgrades: OnUpgrade) { }); } pub async fn start_connecteable_handler(connection: Arc) { + use futures::SinkExt; + use tokio::time::Duration; + + const IDLE_TIMEOUT: Duration = Duration::from_secs(30); + loop { - let msg = match { - let mut receiver = connection.receiver.write().await; - receiver.next().await - } { - Some(Ok(msg)) => msg, - Some(Err(e)) => { + let mut receiver = connection.receiver.write().await; + + match tokio::time::timeout(IDLE_TIMEOUT, receiver.next()).await { + Ok(Some(Ok(msg))) => { + // Drop the lock so other tasks can use the receiver if needed, + // and so we can handle the message without holding the lock. + drop(receiver); + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + Message::Pong(_) => { + // Received a pong, connection is alive. + } + _ => {} + } + } + Ok(Some(Err(e))) => { log!("WS Error: {}", e); break; } - _ => break, - }; - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Close(_) => { + Ok(None) => { + // Stream is closed break; } - _ => {} + Err(_) => { + // Timeout, we need to send a ping. + // Drop receiver lock before acquiring sender lock to avoid deadlock. + drop(receiver); + log!("WebSocket connection is idle. Sending a ping."); + let mut sender = connection.sender.write().await; + if let Err(e) = sender + .send(Message::Text(Utf8Bytes::from( + CommunicationValue::new(CommunicationType::ping) + .to_json() + .to_string(), + ))) + .await + { + log!("Failed to send ping: {}. Closing connection.", e); + break; + } + } } } connection.handle_close().await; From 0917d6b9198c29e705bab973f33e04ff1a93aa68 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 10 Feb 2026 21:24:14 +0100 Subject: [PATCH 054/220] [Add] AI Keep alive + Human Bugfix --- src/server/socket.rs | 71 ++++++++++++++++++++++++++++++++------------ 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/src/server/socket.rs b/src/server/socket.rs index 6b48154..f18f525 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -4,8 +4,9 @@ use futures::StreamExt; use hyper::upgrade::OnUpgrade; use hyper_util::rt::TokioIo; use tokio_tungstenite::WebSocketStream; -use tungstenite::Message; +use tungstenite::{Message, Utf8Bytes}; +use crate::data::communication::{CommunicationType, CommunicationValue}; use crate::log; use crate::server::omikron_connection::OmikronConnection; @@ -40,30 +41,62 @@ pub fn handle(path: String, upgrades: OnUpgrade) { }); } pub async fn start_connecteable_handler(connection: Arc) { + use futures::SinkExt; + use tokio::time::Duration; + + const IDLE_TIMEOUT: Duration = Duration::from_secs(30); + loop { - let msg = match { - let mut receiver = connection.receiver.write().await; - receiver.next().await - } { - Some(Ok(msg)) => msg, - Some(Err(e)) => { + let mut receiver = connection.receiver.write().await; + + match tokio::time::timeout(IDLE_TIMEOUT, receiver.next()).await { + Ok(Some(Ok(msg))) => { + // Drop the lock so other tasks can use the receiver if needed, + // and so we can handle the message without holding the lock. + drop(receiver); + + match msg { + Message::Text(text) => { + let conn_clone = connection.clone(); + tokio::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); + } + Message::Close(_) => { + break; + } + Message::Pong(_) => { + // Received a pong, connection is alive. + } + _ => {} + } + } + Ok(Some(Err(e))) => { log!("WS Error: {}", e); break; } - _ => break, - }; - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Close(_) => { + Ok(None) => { + // Stream is closed break; } - _ => {} + Err(_) => { + // Timeout, we need to send a ping. + // Drop receiver lock before acquiring sender lock to avoid deadlock. + drop(receiver); + log!("WebSocket connection is idle. Sending a ping."); + let mut sender = connection.sender.write().await; + if let Err(e) = sender + .send(Message::Text(Utf8Bytes::from( + CommunicationValue::new(CommunicationType::ping) + .to_json() + .to_string(), + ))) + .await + { + log!("Failed to send ping: {}. Closing connection.", e); + break; + } + } } } connection.handle_close().await; From 7f78c8669b36cbe39755d69cccd6971e56e10290 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:33:09 +0100 Subject: [PATCH 055/220] [Fix] Now using Axum instead of Hyper and Http2 instead of 1 --- Cargo.lock | 3 + Cargo.toml | 2 +- src/server/api.rs | 6 +- src/server/omikron_connection.rs | 37 ++- src/server/omikron_manager.rs | 8 - src/server/server.rs | 522 +++++-------------------------- src/server/socket.rs | 46 +-- 7 files changed, 108 insertions(+), 516 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b58524..ba72ade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -408,8 +409,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", diff --git a/Cargo.toml b/Cargo.toml index 22121da..f89a8a4 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = "0.8.8" +axum = { version = "0.8.8", features = [ "ws" ] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" diff --git a/src/server/api.rs b/src/server/api.rs index 35ee9ec..ae30a24 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -10,14 +10,12 @@ use crate::{ }; use axum::http::HeaderValue; use base64::Engine as _; -use http_body_util::{Full, StreamBody}; -use hyper::body::{Body, Bytes, Frame}; +use http_body_util::Full; +use hyper::body::Bytes; use hyper::header::{CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_TYPE}; use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use json::JsonValue; use json::number::Number; -use tokio::fs::File; -use tokio_util::io::ReaderStream; pub async fn handle( path: &str, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 7ce8f8b..3eeb22b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -6,29 +6,26 @@ use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_in, log_out}; +use crate::{get_private_key, get_public_key, log_err, log_in, log_out}; +use axum::extract::ws::WebSocket; +use axum::extract::ws::{Message, Utf8Bytes}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use futures::SinkExt; use futures::stream::SplitSink; use futures::stream::SplitStream; -use hyper::upgrade::Upgraded; -use hyper_util::rt::TokioIo; +use futures_util::SinkExt; use json::JsonValue; use json::number::Number; use rand::Rng; use rand::distributions::Alphanumeric; use std::sync::Arc; use tokio::sync::RwLock; -use tokio_tungstenite::WebSocketStream; -use tungstenite::Message; -use tungstenite::Utf8Bytes; use uuid::Uuid; use x448::PublicKey; pub struct OmikronConnection { - pub sender: Arc>, Message>>>, - pub receiver: Arc>>>>, + pub sender: Arc>>, + pub receiver: Arc>>, pub omikron_id: Arc>, pub pub_key: Arc>>>, identified: Arc>, @@ -43,8 +40,8 @@ pub struct OmikronConnection { impl OmikronConnection { pub fn new( - sender: SplitSink>, Message>, - receiver: SplitStream>>, + sender: SplitSink, + receiver: SplitStream, ) -> Arc { Arc::new(Self { sender: Arc::new(RwLock::new(sender)), @@ -66,10 +63,17 @@ impl OmikronConnection { *self.omikron_id.read().await, PrintType::Omikron, "{}", - message_text + cv.to_json().to_string() + ); + } + if let Err(e) = sender.send(message_text).await { + log_err!( + *self.omikron_id.read().await, + PrintType::Omikron, + "WebSocket send error: {}", + e ); } - let _ = sender.send(message_text).await; } pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await @@ -77,9 +81,6 @@ impl OmikronConnection { pub async fn is_identified(&self) -> bool { *self.identified.read().await && *self.challenged.read().await } - pub async fn get_public_key(&self) -> PublicKey { - PublicKey::from_bytes(self.pub_key.read().await.as_ref().unwrap()).unwrap() - } pub async fn handle_message(self: Arc, message: String) { let cv = CommunicationValue::from_json(&message); @@ -619,7 +620,6 @@ impl OmikronConnection { if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { if let Some(iota_id) = iota_id_opt { - // Existing logic to update iota match sql::register_complete_iota(iota_id, public_key.to_string()).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success) @@ -792,7 +792,7 @@ impl OmikronConnection { ) { match sql::get_by_user_id(user_id).await { Ok(user) => { - let current_token = user.11; // token is the 12th element (index 11) + let current_token = user.11; if current_token == reset_token { let mut success = true; let mut error_message = String::new(); @@ -824,7 +824,6 @@ impl OmikronConnection { } else { self.send_error_response( &cv.get_id(), - // Using this for invalid token CommunicationType::error_invalid_challenge, ) .await; diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index 8440835..864c9d6 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -24,11 +24,3 @@ pub async fn get_random_omikron() -> Result, ()> { return Err(()); } } - -pub async fn get_omikron(omikron_id: i64) -> Option> { - if let Some(omikron) = OMIKRON_CONNECTIONS.get(&omikron_id) { - Some(omikron.clone()) - } else { - None - } -} diff --git a/src/server/server.rs b/src/server/server.rs index 3926f08..95af459 100755 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,486 +1,106 @@ -use base64::Engine; -use base64::engine::general_purpose::STANDARD; -use bytes::Bytes; -use futures::StreamExt; -use futures_util::TryFutureExt; -use http_body_util::BodyExt; -use http_body_util::Full; -use hyper::server::conn::http2; -use hyper::{Method, StatusCode}; -use hyper::{ - Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade, +use axum::{ + Router, + body::Body, + extract::{ConnectInfo, OriginalUri, Path, ws::WebSocketUpgrade}, + response::{IntoResponse, Redirect}, + routing::get, }; -use hyper_util::rt::TokioExecutor; -use hyper_util::rt::tokio::TokioIo; -use hyper_util::service::TowerToHyperService; + use pnet::datalink::NetworkInterface; -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use sha1::{Digest, Sha1}; -use std::error::Error; -use std::io::ErrorKind; -use std::io::{self, BufReader}; -use std::net::{IpAddr, SocketAddr}; -use std::result::Result::Ok; -use std::sync::Arc; -use std::{future::Future, pin::Pin, time::Duration}; +use std::net::SocketAddr; +use std::time::Duration; use tokio::net::TcpListener; -use tokio::sync::broadcast; -use tokio_rustls::TlsAcceptor; -use tower::Service; use crate::log; use crate::server::api; use crate::server::short_link::get_short_link; use crate::server::socket; -use crate::util::file_util::load_file_buf; -#[derive(Clone)] -struct HttpService { - peer_addr: SocketAddr, + +pub async fn start(port: u16) -> bool { + let app = Router::new() + .route("/ws/omikron", get(ws_handler)) + .route("/direct/{short}", get(direct_handler)) + .fallback(fallback_handler); + run_http_server(port, app).await } -impl Service> for HttpService { - type Response = HttpResponse>; - type Error = io::Error; - type Future = Pin> + Send>>; +async fn ws_handler( + ws: WebSocketUpgrade, + OriginalUri(uri): OriginalUri, + ConnectInfo(_): ConnectInfo, +) -> impl IntoResponse { + log!("Attempting WebSocket upgrade on {}", uri.path()); + let path = uri.path().to_string(); - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(std::io::Result::Ok(())) - } + ws.on_upgrade(async move |socket| socket::handle(path, socket)) +} - fn call(&mut self, req: HttpRequest) -> Self::Future { - let peer_ip = self.peer_addr.ip(); - - let (parts, body) = req.into_parts(); - - let method = parts.method.clone(); - let path = parts.uri.path().to_string(); - let headers = parts.headers.clone(); - - let fut = async move { - let is_websocket_upgrade = path.starts_with("/ws") - && method == Method::GET - && headers - .get("connection") - .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) - .unwrap_or(false) - && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); - - if is_websocket_upgrade { - log!("Attempting WebSocket upgrade on /ws"); - - if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { - let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string(); - let sec_websocket_accept = calculate_accept_key(&sec_websocket_key); - - let response = HttpResponse::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header("Upgrade", "websocket") - .header("Connection", "Upgrade") - .header("Sec-WebSocket-Accept", sec_websocket_accept) - .body(Full::new(Bytes::from(""))) - .unwrap(); - let req_for_upgrade = HttpRequest::from_parts(parts, body); - let upgrades = upgrade::on(req_for_upgrade); - - socket::handle(path, upgrades); - Ok(response) - } else { - log!("No Sec-WebSocket-Key found in request headers"); - let response = HttpResponse::builder() - .status(StatusCode::BAD_REQUEST) - .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) - .unwrap(); - Ok(response) - } - } else if path.starts_with("/api") { - let whole_body = - tokio::time::timeout(Duration::from_secs(10), body.collect()).await??; - let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers.clone(), body_string).await) - } else if path.starts_with("/direct") { - let short = path.replace("/direct/", ""); - if let Ok(long) = get_short_link(&short).await { - let response = HttpResponse::builder() - .status(StatusCode::FOUND) - .header("Location", long) - .body(Full::new(Bytes::from(""))) - .unwrap(); - Ok(response) - } else { - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from("Short link not found"))) - .unwrap(); - Ok(response) - } - } else { - let whole_body = match body.collect().await { - Ok(collected) => collected, - Err(e) => { - log!("Error collecting body: {}", e); - return Ok(HttpResponse::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::from(format!( - "Failed to read body: {}", - e - )))) - .unwrap()); - } - }; - let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers, body_string).await) - } - }; - - Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { - io::Error::new( - io::ErrorKind::Other, - format!("Error in request handling: {}", err), - ) - })) +async fn direct_handler(Path(short): Path) -> impl IntoResponse { + match get_short_link(&short).await { + Ok(long) => Redirect::temporary(&long), + Err(_) => Redirect::temporary("https://tensamin.net"), } } -pub fn is_local_network(addr: IpAddr) -> bool { - match addr { - IpAddr::V4(v4) => { - let octets = v4.octets(); - if octets[0] == 10 { - return true; - } - if octets[0] == 172 && (16..=31).contains(&octets[1]) { - return true; - } - if octets[0] == 192 && octets[1] == 168 { - return true; - } - if octets[0] == 127 { - return true; - } - if octets[0] == 169 && octets[1] == 254 { - return true; - } +async fn fallback_handler( + OriginalUri(uri): OriginalUri, + headers: axum::http::HeaderMap, + body: Body, +) -> impl IntoResponse { + let path = uri.path().to_string(); - false - } + let whole_body = tokio::time::timeout( + Duration::from_secs(10), + axum::body::to_bytes(body, 1024 * 1024 * 10), + ) + .await; - IpAddr::V6(v6) => { - let segments = v6.segments(); - if (segments[0] & 0xfe00) == 0xfc00 { - return true; - } - if (segments[0] & 0xffc0) == 0xfe80 { - return true; - } - if v6.is_loopback() { - return true; - } + let body_string = match whole_body { + Ok(Ok(bytes)) => String::from_utf8(bytes.to_vec()).ok(), + _ => None, + }; - false - } - } + api::handle(&path, headers, body_string).await } -async fn run_http_server(port: u16) -> bool { - let mut ip = "0.0.0.0".to_string(); - for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - if iface.ips.len() > 0 { - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - if format!("{}", ips).starts_with("10.") || format!("{}", ips).starts_with("192.") { - ip = ips.to_string(); - } +async fn run_http_server(port: u16, app: Router) -> bool { + let ip = find_local_ip(); + let listener = match TcpListener::bind(format!("0.0.0.0:{}", port)).await { + Ok(l) => l, + Err(e) => { + log!("Failed to bind to port {}: {:?}", port, e); + return false; } - } - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); - return false; - } - let listener = listener.unwrap(); + }; + log!( "Standard Server listening for HTTP and WS on {}:{}", ip, port ); - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); - - tokio::spawn(async move { - loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Standard Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let io = TokioIo::new(stream); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - use hyper::server::conn::http2; - - let conn = http2::Builder::new(TokioExecutor::new()) - .serve_connection(io, TowerToHyperService::new(service)); - - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; - } - } - } - } - } - - log!("Standard Server shutdown complete."); - }); - - true + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .await + .map(|_| true) + .unwrap_or_else(|e| { + log!("Server error: {}", e); + false + }) } -/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config. -async fn run_tls_server(port: u16, tls_config: Arc) -> bool { - let mut ip = "0.0.0.0".to_string(); +fn find_local_ip() -> String { for iface in pnet::datalink::interfaces() { let iface: NetworkInterface = iface; - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - log!("{}", ips); - if format!("{}", ips).starts_with("10.") { - ip = ips.to_string(); - } - } - - let acceptor = TlsAcceptor::from(tls_config); - - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); - return false; - } - let listener = listener.unwrap(); - log!( - "Encrypted Server listening for HTTPS and WSS on {}:{}", - ip, - port - ); - - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); - - tokio::spawn(async move { - loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Encrypted Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let acceptor = acceptor.clone(); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - // Perform TLS handshake - let tls_stream = match acceptor.accept(stream).await { - Ok(s) => s, - Err(e) => { - if e.kind() != io::ErrorKind::Interrupted { - log!("TLS Handshake error: {:?}", e); - } - return; - } - }; - let io = TokioIo::new(tls_stream); - - // Prepare connection future - let conn = http1::Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .serve_connection(io, TowerToHyperService::new(service)) - .with_upgrades(); - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; - } - } - } + if !iface.ips.is_empty() { + let ipsv = format!("{}", iface.ips[0]); + let ips: &str = ipsv.split('/').next().unwrap(); + if ips.starts_with("10.") || ips.starts_with("192.") { + return ips.to_string(); } } - - log!("Encrypted Server shutdown complete."); - }); - true -} - -pub async fn start(port: u16) -> bool { - let tls_result = load_tls_config(); - - match tls_result { - Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, - Ok(_) => run_http_server(port).await, - Err(e) => { - log!("Fatal error during TLS config load: {}", e); - false - } } -} -fn calculate_accept_key(key: &str) -> String { - let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(key.as_bytes()); - sha1.update(websocket_guid.as_bytes()); - let result = sha1.finalize(); - STANDARD.encode(result) // Base64 encode the result -} - -/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. -fn load_tls_config() -> Result>, Box> { - let cert_file_res = load_file_buf("certs", "cert.pem"); - let key_file_res = load_file_buf("certs", "cert.key"); - - // Check if certificate files are present. If not, return None. - let cert_file_buf = match cert_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS certificate 'certs/cert.pem' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - let key_file_buf = match key_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS key 'certs/cert.key' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - // Continue with configuration if both files were found - let mut cert_reader = BufReader::new(cert_file_buf); - let cert_ders = rustls_pemfile::certs(&mut cert_reader) - .collect::, io::Error>>()?; - - // PKCS8 - let mut key_reader = BufReader::new(key_file_buf); - let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion - .collect::, io::Error>>()?; - - if key_ders.is_empty() { - // RSA - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - // EC - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); - } - - let mut config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_ders, key_ders.remove(0))?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - Ok(Some(Arc::new(config))) + "0.0.0.0".to_string() } diff --git a/src/server/socket.rs b/src/server/socket.rs index 5a8b815..25a00f7 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,51 +1,31 @@ use std::sync::Arc; +use axum::extract::ws::{Message, Utf8Bytes, WebSocket}; use futures::StreamExt; -use hyper::upgrade::OnUpgrade; -use hyper_util::rt::TokioIo; -use tokio_tungstenite::WebSocketStream; -use tungstenite::{Message, Utf8Bytes}; use crate::data::communication::{CommunicationType, CommunicationValue}; use crate::log; use crate::server::omikron_connection::OmikronConnection; -pub fn handle(path: String, upgrades: OnUpgrade) { +pub fn handle(path: String, upgrades: WebSocket) { tokio::spawn(async move { log!( "[ws] Spawning new task to handle WebSocket upgrade for path: {}", path ); - match upgrades.await { - Ok(upgraded_stream) => { - log!("[ws] WebSocket upgrade successful for path: {}", path); - let raw_stream = TokioIo::new(upgraded_stream); + log!("[ws] WebSocket upgrade successful for path: {}", path); - let ws_stream = WebSocketStream::from_raw_socket( - raw_stream, - tungstenite::protocol::Role::Server, - None, - ) - .await; - log!( - "[ws] WebSocket handshake successful, handling connection for {}", - path - ); + log!( + "[ws] WebSocket handshake successful, handling connection for {}", + path + ); - let (writer, reader) = ws_stream.split(); - if path == "/ws/omikron" { - let connection = OmikronConnection::new(writer, reader); - tokio::spawn(start_connecteable_handler(connection)); - } - } - Err(e) => { - log!( - "[ERROR] WebSocket upgrade failed for path {}: {:?}", - path, - e - ); - } + let (writer, reader) = upgrades.split(); + if path == "/ws/omikron" { + let connection = OmikronConnection::new(writer, reader); + tokio::spawn(start_connecteable_handler(connection)); } + log!( "[ws] WebSocket handling task for path: {} is finished.", path @@ -89,7 +69,7 @@ pub async fn start_connecteable_handler(connection: Arc) { log!("[ERROR] WS Error: {}. Breaking loop.", e); break; } - Ok(None) => { + Ok(_) => { log!("[ws_handler] WebSocket stream closed by peer. Breaking loop."); break; } From 169927b3683c414292c965f4c2c274260036019b Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:33:09 +0100 Subject: [PATCH 056/220] [Fix] Now using Axum instead of Hyper and Http2 instead of 1 --- Cargo.lock | 3 + Cargo.toml | 2 +- src/server/api.rs | 6 +- src/server/omikron_connection.rs | 37 ++- src/server/omikron_manager.rs | 8 - src/server/server.rs | 522 +++++-------------------------- src/server/socket.rs | 46 +-- 7 files changed, 108 insertions(+), 516 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b58524..ba72ade 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -390,6 +390,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", + "base64 0.22.1", "bytes", "form_urlencoded", "futures-util", @@ -408,8 +409,10 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", + "sha1", "sync_wrapper", "tokio", + "tokio-tungstenite", "tower", "tower-layer", "tower-service", diff --git a/Cargo.toml b/Cargo.toml index 22121da..f89a8a4 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = "0.8.8" +axum = { version = "0.8.8", features = [ "ws" ] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" diff --git a/src/server/api.rs b/src/server/api.rs index 35ee9ec..ae30a24 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -10,14 +10,12 @@ use crate::{ }; use axum::http::HeaderValue; use base64::Engine as _; -use http_body_util::{Full, StreamBody}; -use hyper::body::{Body, Bytes, Frame}; +use http_body_util::Full; +use hyper::body::Bytes; use hyper::header::{CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_TYPE}; use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use json::JsonValue; use json::number::Number; -use tokio::fs::File; -use tokio_util::io::ReaderStream; pub async fn handle( path: &str, diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 7ce8f8b..3eeb22b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -6,29 +6,26 @@ use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_in, log_out}; +use crate::{get_private_key, get_public_key, log_err, log_in, log_out}; +use axum::extract::ws::WebSocket; +use axum::extract::ws::{Message, Utf8Bytes}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use futures::SinkExt; use futures::stream::SplitSink; use futures::stream::SplitStream; -use hyper::upgrade::Upgraded; -use hyper_util::rt::TokioIo; +use futures_util::SinkExt; use json::JsonValue; use json::number::Number; use rand::Rng; use rand::distributions::Alphanumeric; use std::sync::Arc; use tokio::sync::RwLock; -use tokio_tungstenite::WebSocketStream; -use tungstenite::Message; -use tungstenite::Utf8Bytes; use uuid::Uuid; use x448::PublicKey; pub struct OmikronConnection { - pub sender: Arc>, Message>>>, - pub receiver: Arc>>>>, + pub sender: Arc>>, + pub receiver: Arc>>, pub omikron_id: Arc>, pub pub_key: Arc>>>, identified: Arc>, @@ -43,8 +40,8 @@ pub struct OmikronConnection { impl OmikronConnection { pub fn new( - sender: SplitSink>, Message>, - receiver: SplitStream>>, + sender: SplitSink, + receiver: SplitStream, ) -> Arc { Arc::new(Self { sender: Arc::new(RwLock::new(sender)), @@ -66,10 +63,17 @@ impl OmikronConnection { *self.omikron_id.read().await, PrintType::Omikron, "{}", - message_text + cv.to_json().to_string() + ); + } + if let Err(e) = sender.send(message_text).await { + log_err!( + *self.omikron_id.read().await, + PrintType::Omikron, + "WebSocket send error: {}", + e ); } - let _ = sender.send(message_text).await; } pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await @@ -77,9 +81,6 @@ impl OmikronConnection { pub async fn is_identified(&self) -> bool { *self.identified.read().await && *self.challenged.read().await } - pub async fn get_public_key(&self) -> PublicKey { - PublicKey::from_bytes(self.pub_key.read().await.as_ref().unwrap()).unwrap() - } pub async fn handle_message(self: Arc, message: String) { let cv = CommunicationValue::from_json(&message); @@ -619,7 +620,6 @@ impl OmikronConnection { if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { if let Some(iota_id) = iota_id_opt { - // Existing logic to update iota match sql::register_complete_iota(iota_id, public_key.to_string()).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success) @@ -792,7 +792,7 @@ impl OmikronConnection { ) { match sql::get_by_user_id(user_id).await { Ok(user) => { - let current_token = user.11; // token is the 12th element (index 11) + let current_token = user.11; if current_token == reset_token { let mut success = true; let mut error_message = String::new(); @@ -824,7 +824,6 @@ impl OmikronConnection { } else { self.send_error_response( &cv.get_id(), - // Using this for invalid token CommunicationType::error_invalid_challenge, ) .await; diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index 8440835..864c9d6 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -24,11 +24,3 @@ pub async fn get_random_omikron() -> Result, ()> { return Err(()); } } - -pub async fn get_omikron(omikron_id: i64) -> Option> { - if let Some(omikron) = OMIKRON_CONNECTIONS.get(&omikron_id) { - Some(omikron.clone()) - } else { - None - } -} diff --git a/src/server/server.rs b/src/server/server.rs index 3926f08..95af459 100755 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,486 +1,106 @@ -use base64::Engine; -use base64::engine::general_purpose::STANDARD; -use bytes::Bytes; -use futures::StreamExt; -use futures_util::TryFutureExt; -use http_body_util::BodyExt; -use http_body_util::Full; -use hyper::server::conn::http2; -use hyper::{Method, StatusCode}; -use hyper::{ - Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade, +use axum::{ + Router, + body::Body, + extract::{ConnectInfo, OriginalUri, Path, ws::WebSocketUpgrade}, + response::{IntoResponse, Redirect}, + routing::get, }; -use hyper_util::rt::TokioExecutor; -use hyper_util::rt::tokio::TokioIo; -use hyper_util::service::TowerToHyperService; + use pnet::datalink::NetworkInterface; -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use sha1::{Digest, Sha1}; -use std::error::Error; -use std::io::ErrorKind; -use std::io::{self, BufReader}; -use std::net::{IpAddr, SocketAddr}; -use std::result::Result::Ok; -use std::sync::Arc; -use std::{future::Future, pin::Pin, time::Duration}; +use std::net::SocketAddr; +use std::time::Duration; use tokio::net::TcpListener; -use tokio::sync::broadcast; -use tokio_rustls::TlsAcceptor; -use tower::Service; use crate::log; use crate::server::api; use crate::server::short_link::get_short_link; use crate::server::socket; -use crate::util::file_util::load_file_buf; -#[derive(Clone)] -struct HttpService { - peer_addr: SocketAddr, + +pub async fn start(port: u16) -> bool { + let app = Router::new() + .route("/ws/omikron", get(ws_handler)) + .route("/direct/{short}", get(direct_handler)) + .fallback(fallback_handler); + run_http_server(port, app).await } -impl Service> for HttpService { - type Response = HttpResponse>; - type Error = io::Error; - type Future = Pin> + Send>>; +async fn ws_handler( + ws: WebSocketUpgrade, + OriginalUri(uri): OriginalUri, + ConnectInfo(_): ConnectInfo, +) -> impl IntoResponse { + log!("Attempting WebSocket upgrade on {}", uri.path()); + let path = uri.path().to_string(); - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(std::io::Result::Ok(())) - } + ws.on_upgrade(async move |socket| socket::handle(path, socket)) +} - fn call(&mut self, req: HttpRequest) -> Self::Future { - let peer_ip = self.peer_addr.ip(); - - let (parts, body) = req.into_parts(); - - let method = parts.method.clone(); - let path = parts.uri.path().to_string(); - let headers = parts.headers.clone(); - - let fut = async move { - let is_websocket_upgrade = path.starts_with("/ws") - && method == Method::GET - && headers - .get("connection") - .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) - .unwrap_or(false) - && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); - - if is_websocket_upgrade { - log!("Attempting WebSocket upgrade on /ws"); - - if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { - let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string(); - let sec_websocket_accept = calculate_accept_key(&sec_websocket_key); - - let response = HttpResponse::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header("Upgrade", "websocket") - .header("Connection", "Upgrade") - .header("Sec-WebSocket-Accept", sec_websocket_accept) - .body(Full::new(Bytes::from(""))) - .unwrap(); - let req_for_upgrade = HttpRequest::from_parts(parts, body); - let upgrades = upgrade::on(req_for_upgrade); - - socket::handle(path, upgrades); - Ok(response) - } else { - log!("No Sec-WebSocket-Key found in request headers"); - let response = HttpResponse::builder() - .status(StatusCode::BAD_REQUEST) - .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) - .unwrap(); - Ok(response) - } - } else if path.starts_with("/api") { - let whole_body = - tokio::time::timeout(Duration::from_secs(10), body.collect()).await??; - let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers.clone(), body_string).await) - } else if path.starts_with("/direct") { - let short = path.replace("/direct/", ""); - if let Ok(long) = get_short_link(&short).await { - let response = HttpResponse::builder() - .status(StatusCode::FOUND) - .header("Location", long) - .body(Full::new(Bytes::from(""))) - .unwrap(); - Ok(response) - } else { - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from("Short link not found"))) - .unwrap(); - Ok(response) - } - } else { - let whole_body = match body.collect().await { - Ok(collected) => collected, - Err(e) => { - log!("Error collecting body: {}", e); - return Ok(HttpResponse::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::from(format!( - "Failed to read body: {}", - e - )))) - .unwrap()); - } - }; - let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers, body_string).await) - } - }; - - Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { - io::Error::new( - io::ErrorKind::Other, - format!("Error in request handling: {}", err), - ) - })) +async fn direct_handler(Path(short): Path) -> impl IntoResponse { + match get_short_link(&short).await { + Ok(long) => Redirect::temporary(&long), + Err(_) => Redirect::temporary("https://tensamin.net"), } } -pub fn is_local_network(addr: IpAddr) -> bool { - match addr { - IpAddr::V4(v4) => { - let octets = v4.octets(); - if octets[0] == 10 { - return true; - } - if octets[0] == 172 && (16..=31).contains(&octets[1]) { - return true; - } - if octets[0] == 192 && octets[1] == 168 { - return true; - } - if octets[0] == 127 { - return true; - } - if octets[0] == 169 && octets[1] == 254 { - return true; - } +async fn fallback_handler( + OriginalUri(uri): OriginalUri, + headers: axum::http::HeaderMap, + body: Body, +) -> impl IntoResponse { + let path = uri.path().to_string(); - false - } + let whole_body = tokio::time::timeout( + Duration::from_secs(10), + axum::body::to_bytes(body, 1024 * 1024 * 10), + ) + .await; - IpAddr::V6(v6) => { - let segments = v6.segments(); - if (segments[0] & 0xfe00) == 0xfc00 { - return true; - } - if (segments[0] & 0xffc0) == 0xfe80 { - return true; - } - if v6.is_loopback() { - return true; - } + let body_string = match whole_body { + Ok(Ok(bytes)) => String::from_utf8(bytes.to_vec()).ok(), + _ => None, + }; - false - } - } + api::handle(&path, headers, body_string).await } -async fn run_http_server(port: u16) -> bool { - let mut ip = "0.0.0.0".to_string(); - for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - if iface.ips.len() > 0 { - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - if format!("{}", ips).starts_with("10.") || format!("{}", ips).starts_with("192.") { - ip = ips.to_string(); - } +async fn run_http_server(port: u16, app: Router) -> bool { + let ip = find_local_ip(); + let listener = match TcpListener::bind(format!("0.0.0.0:{}", port)).await { + Ok(l) => l, + Err(e) => { + log!("Failed to bind to port {}: {:?}", port, e); + return false; } - } - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); - return false; - } - let listener = listener.unwrap(); + }; + log!( "Standard Server listening for HTTP and WS on {}:{}", ip, port ); - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); - - tokio::spawn(async move { - loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Standard Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let io = TokioIo::new(stream); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - use hyper::server::conn::http2; - - let conn = http2::Builder::new(TokioExecutor::new()) - .serve_connection(io, TowerToHyperService::new(service)); - - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; - } - } - } - } - } - - log!("Standard Server shutdown complete."); - }); - - true + axum::serve( + listener, + app.into_make_service_with_connect_info::(), + ) + .await + .map(|_| true) + .unwrap_or_else(|e| { + log!("Server error: {}", e); + false + }) } -/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config. -async fn run_tls_server(port: u16, tls_config: Arc) -> bool { - let mut ip = "0.0.0.0".to_string(); +fn find_local_ip() -> String { for iface in pnet::datalink::interfaces() { let iface: NetworkInterface = iface; - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - log!("{}", ips); - if format!("{}", ips).starts_with("10.") { - ip = ips.to_string(); - } - } - - let acceptor = TlsAcceptor::from(tls_config); - - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); - return false; - } - let listener = listener.unwrap(); - log!( - "Encrypted Server listening for HTTPS and WSS on {}:{}", - ip, - port - ); - - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); - - tokio::spawn(async move { - loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Encrypted Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let acceptor = acceptor.clone(); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - // Perform TLS handshake - let tls_stream = match acceptor.accept(stream).await { - Ok(s) => s, - Err(e) => { - if e.kind() != io::ErrorKind::Interrupted { - log!("TLS Handshake error: {:?}", e); - } - return; - } - }; - let io = TokioIo::new(tls_stream); - - // Prepare connection future - let conn = http1::Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .serve_connection(io, TowerToHyperService::new(service)) - .with_upgrades(); - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; - } - } - } + if !iface.ips.is_empty() { + let ipsv = format!("{}", iface.ips[0]); + let ips: &str = ipsv.split('/').next().unwrap(); + if ips.starts_with("10.") || ips.starts_with("192.") { + return ips.to_string(); } } - - log!("Encrypted Server shutdown complete."); - }); - true -} - -pub async fn start(port: u16) -> bool { - let tls_result = load_tls_config(); - - match tls_result { - Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, - Ok(_) => run_http_server(port).await, - Err(e) => { - log!("Fatal error during TLS config load: {}", e); - false - } } -} -fn calculate_accept_key(key: &str) -> String { - let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(key.as_bytes()); - sha1.update(websocket_guid.as_bytes()); - let result = sha1.finalize(); - STANDARD.encode(result) // Base64 encode the result -} - -/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. -fn load_tls_config() -> Result>, Box> { - let cert_file_res = load_file_buf("certs", "cert.pem"); - let key_file_res = load_file_buf("certs", "cert.key"); - - // Check if certificate files are present. If not, return None. - let cert_file_buf = match cert_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS certificate 'certs/cert.pem' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - let key_file_buf = match key_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS key 'certs/cert.key' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - // Continue with configuration if both files were found - let mut cert_reader = BufReader::new(cert_file_buf); - let cert_ders = rustls_pemfile::certs(&mut cert_reader) - .collect::, io::Error>>()?; - - // PKCS8 - let mut key_reader = BufReader::new(key_file_buf); - let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion - .collect::, io::Error>>()?; - - if key_ders.is_empty() { - // RSA - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - // EC - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); - } - - let mut config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_ders, key_ders.remove(0))?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - Ok(Some(Arc::new(config))) + "0.0.0.0".to_string() } diff --git a/src/server/socket.rs b/src/server/socket.rs index 5a8b815..25a00f7 100644 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,51 +1,31 @@ use std::sync::Arc; +use axum::extract::ws::{Message, Utf8Bytes, WebSocket}; use futures::StreamExt; -use hyper::upgrade::OnUpgrade; -use hyper_util::rt::TokioIo; -use tokio_tungstenite::WebSocketStream; -use tungstenite::{Message, Utf8Bytes}; use crate::data::communication::{CommunicationType, CommunicationValue}; use crate::log; use crate::server::omikron_connection::OmikronConnection; -pub fn handle(path: String, upgrades: OnUpgrade) { +pub fn handle(path: String, upgrades: WebSocket) { tokio::spawn(async move { log!( "[ws] Spawning new task to handle WebSocket upgrade for path: {}", path ); - match upgrades.await { - Ok(upgraded_stream) => { - log!("[ws] WebSocket upgrade successful for path: {}", path); - let raw_stream = TokioIo::new(upgraded_stream); + log!("[ws] WebSocket upgrade successful for path: {}", path); - let ws_stream = WebSocketStream::from_raw_socket( - raw_stream, - tungstenite::protocol::Role::Server, - None, - ) - .await; - log!( - "[ws] WebSocket handshake successful, handling connection for {}", - path - ); + log!( + "[ws] WebSocket handshake successful, handling connection for {}", + path + ); - let (writer, reader) = ws_stream.split(); - if path == "/ws/omikron" { - let connection = OmikronConnection::new(writer, reader); - tokio::spawn(start_connecteable_handler(connection)); - } - } - Err(e) => { - log!( - "[ERROR] WebSocket upgrade failed for path {}: {:?}", - path, - e - ); - } + let (writer, reader) = upgrades.split(); + if path == "/ws/omikron" { + let connection = OmikronConnection::new(writer, reader); + tokio::spawn(start_connecteable_handler(connection)); } + log!( "[ws] WebSocket handling task for path: {} is finished.", path @@ -89,7 +69,7 @@ pub async fn start_connecteable_handler(connection: Arc) { log!("[ERROR] WS Error: {}. Breaking loop.", e); break; } - Ok(None) => { + Ok(_) => { log!("[ws_handler] WebSocket stream closed by peer. Breaking loop."); break; } From bf5743fd57f2cd8397ce97fdc7c0081c6b861feb Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:16:33 +0100 Subject: [PATCH 057/220] [TEMP] Split Ws & Http server --- src/main.rs | 5 +- src/server/server.rs | 531 ++++++++++++++----------------------------- 2 files changed, 170 insertions(+), 366 deletions(-) diff --git a/src/main.rs b/src/main.rs index 07b461c..aa6a3f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,8 @@ async fn main() { } else { log!(" Users"); } - server::server::start(9187).await; - log!(" Server"); + server::server::start(9187, 9188).await; + log!(" API Server on 9187"); + log!(" WS Server on 9188"); tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/server.rs b/src/server/server.rs index 3926f08..22c24c6 100755 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,45 +1,36 @@ use base64::Engine; use base64::engine::general_purpose::STANDARD; use bytes::Bytes; -use futures::StreamExt; use futures_util::TryFutureExt; use http_body_util::BodyExt; use http_body_util::Full; -use hyper::server::conn::http2; +use hyper::server::conn::{http1, http2}; use hyper::{Method, StatusCode}; -use hyper::{ - Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade, -}; +use hyper::{Request as HttpRequest, Response as HttpResponse, body::Incoming, upgrade}; use hyper_util::rt::TokioExecutor; use hyper_util::rt::tokio::TokioIo; use hyper_util::service::TowerToHyperService; -use pnet::datalink::NetworkInterface; -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use sha1::{Digest, Sha1}; -use std::error::Error; -use std::io::ErrorKind; -use std::io::{self, BufReader}; -use std::net::{IpAddr, SocketAddr}; +use std::io; +use std::net::SocketAddr; use std::result::Result::Ok; -use std::sync::Arc; use std::{future::Future, pin::Pin, time::Duration}; use tokio::net::TcpListener; -use tokio::sync::broadcast; -use tokio_rustls::TlsAcceptor; use tower::Service; use crate::log; use crate::server::api; use crate::server::short_link::get_short_link; use crate::server::socket; -use crate::util::file_util::load_file_buf; + +// --- ApiService for HTTP/2 --- + #[derive(Clone)] -struct HttpService { - peer_addr: SocketAddr, +struct ApiService { + _peer_addr: SocketAddr, } -impl Service> for HttpService { +impl Service> for ApiService { type Response = HttpResponse>; type Error = io::Error; type Future = Pin> + Send>>; @@ -52,61 +43,17 @@ impl Service> for HttpService { } fn call(&mut self, req: HttpRequest) -> Self::Future { - let peer_ip = self.peer_addr.ip(); - let (parts, body) = req.into_parts(); - - let method = parts.method.clone(); let path = parts.uri.path().to_string(); let headers = parts.headers.clone(); let fut = async move { - let is_websocket_upgrade = path.starts_with("/ws") - && method == Method::GET - && headers - .get("connection") - .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) - .unwrap_or(false) - && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); - - if is_websocket_upgrade { - log!("Attempting WebSocket upgrade on /ws"); - - if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { - let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string(); - let sec_websocket_accept = calculate_accept_key(&sec_websocket_key); - - let response = HttpResponse::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header("Upgrade", "websocket") - .header("Connection", "Upgrade") - .header("Sec-WebSocket-Accept", sec_websocket_accept) - .body(Full::new(Bytes::from(""))) - .unwrap(); - let req_for_upgrade = HttpRequest::from_parts(parts, body); - let upgrades = upgrade::on(req_for_upgrade); - - socket::handle(path, upgrades); - Ok(response) - } else { - log!("No Sec-WebSocket-Key found in request headers"); - let response = HttpResponse::builder() - .status(StatusCode::BAD_REQUEST) - .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) - .unwrap(); - Ok(response) - } - } else if path.starts_with("/api") { + if path.starts_with("/api") { let whole_body = tokio::time::timeout(Duration::from_secs(10), body.collect()).await??; let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers.clone(), body_string).await) + let body_string: Option = String::from_utf8(bytes.to_vec()).ok(); + Ok(api::handle(&path, headers, body_string).await) } else if path.starts_with("/direct") { let short = path.replace("/direct/", ""); if let Ok(long) = get_short_link(&short).await { @@ -124,363 +71,219 @@ impl Service> for HttpService { Ok(response) } } else { - let whole_body = match body.collect().await { - Ok(collected) => collected, - Err(e) => { - log!("Error collecting body: {}", e); - return Ok(HttpResponse::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::from(format!( - "Failed to read body: {}", - e - )))) - .unwrap()); - } - }; - let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers, body_string).await) + // Not a WebSocket, /api, or /direct, so it's a 404 + let response = HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from("Not Found"))) + .unwrap(); + Ok(response) } }; Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { io::Error::new( io::ErrorKind::Other, - format!("Error in request handling: {}", err), + format!("Error in API request: {}", err), ) })) } } -pub fn is_local_network(addr: IpAddr) -> bool { - match addr { - IpAddr::V4(v4) => { - let octets = v4.octets(); - if octets[0] == 10 { - return true; - } - if octets[0] == 172 && (16..=31).contains(&octets[1]) { - return true; - } - if octets[0] == 192 && octets[1] == 168 { - return true; - } - if octets[0] == 127 { - return true; - } - if octets[0] == 169 && octets[1] == 254 { - return true; - } +// --- WebSocketService for HTTP/1.1 --- - false - } +#[derive(Clone)] +struct WebSocketService { + _peer_addr: SocketAddr, +} - IpAddr::V6(v6) => { - let segments = v6.segments(); - if (segments[0] & 0xfe00) == 0xfc00 { - return true; - } - if (segments[0] & 0xffc0) == 0xfe80 { - return true; - } - if v6.is_loopback() { - return true; - } +impl Service> for WebSocketService { + type Response = HttpResponse>; + type Error = io::Error; + type Future = Pin> + Send>>; - false - } + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(std::io::Result::Ok(())) + } + + fn call(&mut self, req: HttpRequest) -> Self::Future { + let (parts, body) = req.into_parts(); + let method = parts.method.clone(); + let path = parts.uri.path().to_string(); + let headers = parts.headers.clone(); + + let fut = async move { + let is_websocket_upgrade = path.starts_with("/ws") + && method == Method::GET + && headers + .get("connection") + .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) + .unwrap_or(false) + && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); + + if is_websocket_upgrade { + log!("Attempting WebSocket upgrade on /ws"); + if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { + let sec_websocket_key_str = + sec_websocket_key.to_str().unwrap_or("").to_string(); + let sec_websocket_accept = calculate_accept_key(&sec_websocket_key_str); + + let response = HttpResponse::builder() + .status(StatusCode::SWITCHING_PROTOCOLS) + .header("Upgrade", "websocket") + .header("Connection", "Upgrade") + .header("Sec-WebSocket-Accept", sec_websocket_accept) + .body(Full::new(Bytes::from(""))) + .unwrap(); + + let req_for_upgrade = HttpRequest::from_parts(parts, body); + let upgrades = upgrade::on(req_for_upgrade); + socket::handle(path, upgrades); + Ok(response) + } else { + log!("No Sec-WebSocket-Key found in request headers"); + let response = HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) + .unwrap(); + Ok(response) + } + } else { + let response = HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from( + "Not Found: Use the API server for non-WebSocket requests.", + ))) + .unwrap(); + Ok(response) + } + }; + + Box::pin(fut.map_err(|_err: color_eyre::eyre::ErrReport| { + io::Error::new(io::ErrorKind::Other, "Error in WebSocket request") + })) } } -async fn run_http_server(port: u16) -> bool { +async fn run_api_server(port: u16) -> bool { let mut ip = "0.0.0.0".to_string(); for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - if iface.ips.len() > 0 { - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - if format!("{}", ips).starts_with("10.") || format!("{}", ips).starts_with("192.") { - ip = ips.to_string(); - } + if let Some(ip_net) = iface.ips.iter().find(|ip_net| { + ip_net.is_ipv4() + && (ip_net.ip().to_string().starts_with("10.") + || ip_net.ip().to_string().starts_with("192.")) + }) { + ip = ip_net.ip().to_string(); + break; } } - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); + + let addr = SocketAddr::new(ip.parse().unwrap(), port); + let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener_res { + log!( + "[FATAL] Failed to bind API server to port {}: {:?}", + port, + e + ); return false; } - let listener = listener.unwrap(); - log!( - "Standard Server listening for HTTP and WS on {}:{}", - ip, - port - ); - - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); + let listener = listener_res.unwrap(); + log!("API Server (HTTP/2) listening on {}:{}", ip, port); tokio::spawn(async move { loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Standard Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let io = TokioIo::new(stream); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - use hyper::server::conn::http2; - - let conn = http2::Builder::new(TokioExecutor::new()) - .serve_connection(io, TowerToHyperService::new(service)); - - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; + if let Ok((stream, addr)) = listener.accept().await { + let service = ApiService { _peer_addr: addr }; + let io = TokioIo::new(stream); + tokio::spawn(async move { + let conn = http2::Builder::new(TokioExecutor::new()) + .serve_connection(io, TowerToHyperService::new(service)); + if let Err(err) = conn.await { + if !err + .to_string() + .starts_with("error shutting down connection") + { + log!("API server connection error: {:?}", err); } } - } + }); } } - - log!("Standard Server shutdown complete."); }); true } -/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config. -async fn run_tls_server(port: u16, tls_config: Arc) -> bool { +async fn run_websocket_server(port: u16) -> bool { let mut ip = "0.0.0.0".to_string(); for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - log!("{}", ips); - if format!("{}", ips).starts_with("10.") { - ip = ips.to_string(); + if let Some(ip_net) = iface.ips.iter().find(|ip_net| { + ip_net.is_ipv4() + && (ip_net.ip().to_string().starts_with("10.") + || ip_net.ip().to_string().starts_with("192.")) + }) { + ip = ip_net.ip().to_string(); + break; } } - let acceptor = TlsAcceptor::from(tls_config); - - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); + let addr = SocketAddr::new(ip.parse().unwrap(), port); + let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener_res { + log!( + "[FATAL] Failed to bind WebSocket server to port {}: {:?}", + port, + e + ); return false; } - let listener = listener.unwrap(); - log!( - "Encrypted Server listening for HTTPS and WSS on {}:{}", - ip, - port - ); - - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); + let listener = listener_res.unwrap(); + log!("WebSocket Server (HTTP/1.1) listening on {}:{}", ip, port); tokio::spawn(async move { loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Encrypted Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let acceptor = acceptor.clone(); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - // Perform TLS handshake - let tls_stream = match acceptor.accept(stream).await { - Ok(s) => s, - Err(e) => { - if e.kind() != io::ErrorKind::Interrupted { - log!("TLS Handshake error: {:?}", e); - } - return; - } - }; - let io = TokioIo::new(tls_stream); - - // Prepare connection future - let conn = http1::Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .serve_connection(io, TowerToHyperService::new(service)) - .with_upgrades(); - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; + if let Ok((stream, addr)) = listener.accept().await { + let service = WebSocketService { _peer_addr: addr }; + let io = TokioIo::new(stream); + tokio::spawn(async move { + let conn = http1::Builder::new() + .preserve_header_case(true) + .title_case_headers(true) + .serve_connection(io, TowerToHyperService::new(service)) + .with_upgrades(); + if let Err(err) = conn.await { + if !err + .to_string() + .starts_with("error shutting down connection") + { + log!("WebSocket server connection error: {:?}", err); } } - } + }); } } - - log!("Encrypted Server shutdown complete."); }); + true } -pub async fn start(port: u16) -> bool { - let tls_result = load_tls_config(); +// --- Main Start function --- - match tls_result { - Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, - Ok(_) => run_http_server(port).await, - Err(e) => { - log!("Fatal error during TLS config load: {}", e); - false - } - } +pub async fn start(api_port: u16, ws_port: u16) { + // We are not using TLS for this setup as per the request's focus on splitting protocols. + // The previous TLS loading logic is removed for simplicity. + tokio::spawn(run_api_server(api_port)); + tokio::spawn(run_websocket_server(ws_port)); } + fn calculate_accept_key(key: &str) -> String { let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; let mut sha1 = Sha1::new(); sha1.update(key.as_bytes()); sha1.update(websocket_guid.as_bytes()); let result = sha1.finalize(); - STANDARD.encode(result) // Base64 encode the result -} - -/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. -fn load_tls_config() -> Result>, Box> { - let cert_file_res = load_file_buf("certs", "cert.pem"); - let key_file_res = load_file_buf("certs", "cert.key"); - - // Check if certificate files are present. If not, return None. - let cert_file_buf = match cert_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS certificate 'certs/cert.pem' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - let key_file_buf = match key_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS key 'certs/cert.key' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - // Continue with configuration if both files were found - let mut cert_reader = BufReader::new(cert_file_buf); - let cert_ders = rustls_pemfile::certs(&mut cert_reader) - .collect::, io::Error>>()?; - - // PKCS8 - let mut key_reader = BufReader::new(key_file_buf); - let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion - .collect::, io::Error>>()?; - - if key_ders.is_empty() { - // RSA - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - // EC - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); - } - - let mut config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_ders, key_ders.remove(0))?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - Ok(Some(Arc::new(config))) + STANDARD.encode(result) } From aab225746f56381fad2d261c44d2860fba8c8aec Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 14 Feb 2026 16:16:33 +0100 Subject: [PATCH 058/220] [TEMP] Split Ws & Http server --- src/main.rs | 5 +- src/server/server.rs | 531 ++++++++++++++----------------------------- 2 files changed, 170 insertions(+), 366 deletions(-) diff --git a/src/main.rs b/src/main.rs index 07b461c..aa6a3f9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,7 +43,8 @@ async fn main() { } else { log!(" Users"); } - server::server::start(9187).await; - log!(" Server"); + server::server::start(9187, 9188).await; + log!(" API Server on 9187"); + log!(" WS Server on 9188"); tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/server.rs b/src/server/server.rs index 3926f08..22c24c6 100755 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,45 +1,36 @@ use base64::Engine; use base64::engine::general_purpose::STANDARD; use bytes::Bytes; -use futures::StreamExt; use futures_util::TryFutureExt; use http_body_util::BodyExt; use http_body_util::Full; -use hyper::server::conn::http2; +use hyper::server::conn::{http1, http2}; use hyper::{Method, StatusCode}; -use hyper::{ - Request as HttpRequest, Response as HttpResponse, body::Incoming, server::conn::http1, upgrade, -}; +use hyper::{Request as HttpRequest, Response as HttpResponse, body::Incoming, upgrade}; use hyper_util::rt::TokioExecutor; use hyper_util::rt::tokio::TokioIo; use hyper_util::service::TowerToHyperService; -use pnet::datalink::NetworkInterface; -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use sha1::{Digest, Sha1}; -use std::error::Error; -use std::io::ErrorKind; -use std::io::{self, BufReader}; -use std::net::{IpAddr, SocketAddr}; +use std::io; +use std::net::SocketAddr; use std::result::Result::Ok; -use std::sync::Arc; use std::{future::Future, pin::Pin, time::Duration}; use tokio::net::TcpListener; -use tokio::sync::broadcast; -use tokio_rustls::TlsAcceptor; use tower::Service; use crate::log; use crate::server::api; use crate::server::short_link::get_short_link; use crate::server::socket; -use crate::util::file_util::load_file_buf; + +// --- ApiService for HTTP/2 --- + #[derive(Clone)] -struct HttpService { - peer_addr: SocketAddr, +struct ApiService { + _peer_addr: SocketAddr, } -impl Service> for HttpService { +impl Service> for ApiService { type Response = HttpResponse>; type Error = io::Error; type Future = Pin> + Send>>; @@ -52,61 +43,17 @@ impl Service> for HttpService { } fn call(&mut self, req: HttpRequest) -> Self::Future { - let peer_ip = self.peer_addr.ip(); - let (parts, body) = req.into_parts(); - - let method = parts.method.clone(); let path = parts.uri.path().to_string(); let headers = parts.headers.clone(); let fut = async move { - let is_websocket_upgrade = path.starts_with("/ws") - && method == Method::GET - && headers - .get("connection") - .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) - .unwrap_or(false) - && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); - - if is_websocket_upgrade { - log!("Attempting WebSocket upgrade on /ws"); - - if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { - let sec_websocket_key = sec_websocket_key.to_str().unwrap_or("").to_string(); - let sec_websocket_accept = calculate_accept_key(&sec_websocket_key); - - let response = HttpResponse::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header("Upgrade", "websocket") - .header("Connection", "Upgrade") - .header("Sec-WebSocket-Accept", sec_websocket_accept) - .body(Full::new(Bytes::from(""))) - .unwrap(); - let req_for_upgrade = HttpRequest::from_parts(parts, body); - let upgrades = upgrade::on(req_for_upgrade); - - socket::handle(path, upgrades); - Ok(response) - } else { - log!("No Sec-WebSocket-Key found in request headers"); - let response = HttpResponse::builder() - .status(StatusCode::BAD_REQUEST) - .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) - .unwrap(); - Ok(response) - } - } else if path.starts_with("/api") { + if path.starts_with("/api") { let whole_body = tokio::time::timeout(Duration::from_secs(10), body.collect()).await??; let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers.clone(), body_string).await) + let body_string: Option = String::from_utf8(bytes.to_vec()).ok(); + Ok(api::handle(&path, headers, body_string).await) } else if path.starts_with("/direct") { let short = path.replace("/direct/", ""); if let Ok(long) = get_short_link(&short).await { @@ -124,363 +71,219 @@ impl Service> for HttpService { Ok(response) } } else { - let whole_body = match body.collect().await { - Ok(collected) => collected, - Err(e) => { - log!("Error collecting body: {}", e); - return Ok(HttpResponse::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Full::new(Bytes::from(format!( - "Failed to read body: {}", - e - )))) - .unwrap()); - } - }; - let bytes = whole_body.to_bytes(); - - let body_string: Option = match String::from_utf8(bytes.to_vec()) { - Ok(s) => Some(s), - Err(_) => None, - }; - - Ok(api::handle(&path, headers, body_string).await) + // Not a WebSocket, /api, or /direct, so it's a 404 + let response = HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from("Not Found"))) + .unwrap(); + Ok(response) } }; Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { io::Error::new( io::ErrorKind::Other, - format!("Error in request handling: {}", err), + format!("Error in API request: {}", err), ) })) } } -pub fn is_local_network(addr: IpAddr) -> bool { - match addr { - IpAddr::V4(v4) => { - let octets = v4.octets(); - if octets[0] == 10 { - return true; - } - if octets[0] == 172 && (16..=31).contains(&octets[1]) { - return true; - } - if octets[0] == 192 && octets[1] == 168 { - return true; - } - if octets[0] == 127 { - return true; - } - if octets[0] == 169 && octets[1] == 254 { - return true; - } +// --- WebSocketService for HTTP/1.1 --- - false - } +#[derive(Clone)] +struct WebSocketService { + _peer_addr: SocketAddr, +} - IpAddr::V6(v6) => { - let segments = v6.segments(); - if (segments[0] & 0xfe00) == 0xfc00 { - return true; - } - if (segments[0] & 0xffc0) == 0xfe80 { - return true; - } - if v6.is_loopback() { - return true; - } +impl Service> for WebSocketService { + type Response = HttpResponse>; + type Error = io::Error; + type Future = Pin> + Send>>; - false - } + fn poll_ready( + &mut self, + _cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::task::Poll::Ready(std::io::Result::Ok(())) + } + + fn call(&mut self, req: HttpRequest) -> Self::Future { + let (parts, body) = req.into_parts(); + let method = parts.method.clone(); + let path = parts.uri.path().to_string(); + let headers = parts.headers.clone(); + + let fut = async move { + let is_websocket_upgrade = path.starts_with("/ws") + && method == Method::GET + && headers + .get("connection") + .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) + .unwrap_or(false) + && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); + + if is_websocket_upgrade { + log!("Attempting WebSocket upgrade on /ws"); + if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { + let sec_websocket_key_str = + sec_websocket_key.to_str().unwrap_or("").to_string(); + let sec_websocket_accept = calculate_accept_key(&sec_websocket_key_str); + + let response = HttpResponse::builder() + .status(StatusCode::SWITCHING_PROTOCOLS) + .header("Upgrade", "websocket") + .header("Connection", "Upgrade") + .header("Sec-WebSocket-Accept", sec_websocket_accept) + .body(Full::new(Bytes::from(""))) + .unwrap(); + + let req_for_upgrade = HttpRequest::from_parts(parts, body); + let upgrades = upgrade::on(req_for_upgrade); + socket::handle(path, upgrades); + Ok(response) + } else { + log!("No Sec-WebSocket-Key found in request headers"); + let response = HttpResponse::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) + .unwrap(); + Ok(response) + } + } else { + let response = HttpResponse::builder() + .status(StatusCode::NOT_FOUND) + .body(Full::new(Bytes::from( + "Not Found: Use the API server for non-WebSocket requests.", + ))) + .unwrap(); + Ok(response) + } + }; + + Box::pin(fut.map_err(|_err: color_eyre::eyre::ErrReport| { + io::Error::new(io::ErrorKind::Other, "Error in WebSocket request") + })) } } -async fn run_http_server(port: u16) -> bool { +async fn run_api_server(port: u16) -> bool { let mut ip = "0.0.0.0".to_string(); for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - if iface.ips.len() > 0 { - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - if format!("{}", ips).starts_with("10.") || format!("{}", ips).starts_with("192.") { - ip = ips.to_string(); - } + if let Some(ip_net) = iface.ips.iter().find(|ip_net| { + ip_net.is_ipv4() + && (ip_net.ip().to_string().starts_with("10.") + || ip_net.ip().to_string().starts_with("192.")) + }) { + ip = ip_net.ip().to_string(); + break; } } - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); + + let addr = SocketAddr::new(ip.parse().unwrap(), port); + let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener_res { + log!( + "[FATAL] Failed to bind API server to port {}: {:?}", + port, + e + ); return false; } - let listener = listener.unwrap(); - log!( - "Standard Server listening for HTTP and WS on {}:{}", - ip, - port - ); - - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); + let listener = listener_res.unwrap(); + log!("API Server (HTTP/2) listening on {}:{}", ip, port); tokio::spawn(async move { loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Standard Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let io = TokioIo::new(stream); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - use hyper::server::conn::http2; - - let conn = http2::Builder::new(TokioExecutor::new()) - .serve_connection(io, TowerToHyperService::new(service)); - - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; + if let Ok((stream, addr)) = listener.accept().await { + let service = ApiService { _peer_addr: addr }; + let io = TokioIo::new(stream); + tokio::spawn(async move { + let conn = http2::Builder::new(TokioExecutor::new()) + .serve_connection(io, TowerToHyperService::new(service)); + if let Err(err) = conn.await { + if !err + .to_string() + .starts_with("error shutting down connection") + { + log!("API server connection error: {:?}", err); } } - } + }); } } - - log!("Standard Server shutdown complete."); }); true } -/// Runs the encrypted HTTPS/WSS server loop using the provided TLS config. -async fn run_tls_server(port: u16, tls_config: Arc) -> bool { +async fn run_websocket_server(port: u16) -> bool { let mut ip = "0.0.0.0".to_string(); for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - log!("{}", ips); - if format!("{}", ips).starts_with("10.") { - ip = ips.to_string(); + if let Some(ip_net) = iface.ips.iter().find(|ip_net| { + ip_net.is_ipv4() + && (ip_net.ip().to_string().starts_with("10.") + || ip_net.ip().to_string().starts_with("192.")) + }) { + ip = ip_net.ip().to_string(); + break; } } - let acceptor = TlsAcceptor::from(tls_config); - - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener { - log!("Failed to bind to port {}: {:?}", port, e); + let addr = SocketAddr::new(ip.parse().unwrap(), port); + let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; + if let Err(e) = listener_res { + log!( + "[FATAL] Failed to bind WebSocket server to port {}: {:?}", + port, + e + ); return false; } - let listener = listener.unwrap(); - log!( - "Encrypted Server listening for HTTPS and WSS on {}:{}", - ip, - port - ); - - // Create a broadcast channel for graceful shutdown signal - let (shutdown_tx, _) = broadcast::channel::<()>(1); + let listener = listener_res.unwrap(); + log!("WebSocket Server (HTTP/1.1) listening on {}:{}", ip, port); tokio::spawn(async move { loop { - tokio::select! { - // Monitor for shutdown signal - _ = async { - loop { - tokio::time::sleep(Duration::from_millis(100)).await; - } - } => { - log!("Encrypted Server received shutdown signal."); - // Send kill signal to all active connection tasks - let _ = shutdown_tx.send(()); - break; - } - - // Accept new connections - accepted = listener.accept() => { - match accepted { - std::result::Result::Ok((stream, addr)) => { - let service = HttpService { peer_addr: addr }; - let acceptor = acceptor.clone(); - - // Subscribe to the shutdown signal for this specific connection - let mut rx = shutdown_tx.subscribe(); - - tokio::spawn(async move { - // Perform TLS handshake - let tls_stream = match acceptor.accept(stream).await { - Ok(s) => s, - Err(e) => { - if e.kind() != io::ErrorKind::Interrupted { - log!("TLS Handshake error: {:?}", e); - } - return; - } - }; - let io = TokioIo::new(tls_stream); - - // Prepare connection future - let conn = http1::Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .serve_connection(io, TowerToHyperService::new(service)) - .with_upgrades(); - - // Wait for either the connection to finish naturally OR the shutdown signal - tokio::select! { - res = conn => { - if let Err(err) = res { - if let Some(io_err) = err.source().and_then(|e| e.downcast_ref::()) { - if io_err.kind() != io::ErrorKind::ConnectionReset - && io_err.kind() != io::ErrorKind::BrokenPipe - { - log!("Error serving connection: {:?}", err); - } - } - } - } - _ = rx.recv() => { - // Shutdown signal received. - // Dropping the 'conn' future here closes the socket immediately. - } - } - }); - } - Err(e) => { - log!("Error accepting connection: {:?}", e); - tokio::time::sleep(Duration::from_millis(500)).await; + if let Ok((stream, addr)) = listener.accept().await { + let service = WebSocketService { _peer_addr: addr }; + let io = TokioIo::new(stream); + tokio::spawn(async move { + let conn = http1::Builder::new() + .preserve_header_case(true) + .title_case_headers(true) + .serve_connection(io, TowerToHyperService::new(service)) + .with_upgrades(); + if let Err(err) = conn.await { + if !err + .to_string() + .starts_with("error shutting down connection") + { + log!("WebSocket server connection error: {:?}", err); } } - } + }); } } - - log!("Encrypted Server shutdown complete."); }); + true } -pub async fn start(port: u16) -> bool { - let tls_result = load_tls_config(); +// --- Main Start function --- - match tls_result { - Ok(Some(tls_config)) => run_tls_server(port, tls_config).await, - Ok(_) => run_http_server(port).await, - Err(e) => { - log!("Fatal error during TLS config load: {}", e); - false - } - } +pub async fn start(api_port: u16, ws_port: u16) { + // We are not using TLS for this setup as per the request's focus on splitting protocols. + // The previous TLS loading logic is removed for simplicity. + tokio::spawn(run_api_server(api_port)); + tokio::spawn(run_websocket_server(ws_port)); } + fn calculate_accept_key(key: &str) -> String { let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; let mut sha1 = Sha1::new(); sha1.update(key.as_bytes()); sha1.update(websocket_guid.as_bytes()); let result = sha1.finalize(); - STANDARD.encode(result) // Base64 encode the result -} - -/// Loads TLS config. Returns Ok(None) if cert files are not found, and an error if parsing fails. -fn load_tls_config() -> Result>, Box> { - let cert_file_res = load_file_buf("certs", "cert.pem"); - let key_file_res = load_file_buf("certs", "cert.key"); - - // Check if certificate files are present. If not, return None. - let cert_file_buf = match cert_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS certificate 'certs/cert.pem' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - let key_file_buf = match key_file_res { - Ok(b) => b, - Err(e) if e.kind() == ErrorKind::NotFound => { - log!("TLS key 'certs/cert.key' not found."); - return Ok(None); - } - Err(e) => return Err(e.into()), // Other IO error - }; - - // Continue with configuration if both files were found - let mut cert_reader = BufReader::new(cert_file_buf); - let cert_ders = rustls_pemfile::certs(&mut cert_reader) - .collect::, io::Error>>()?; - - // PKCS8 - let mut key_reader = BufReader::new(key_file_buf); - let mut key_ders = rustls_pemfile::pkcs8_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) // Explicit conversion - .collect::, io::Error>>()?; - - if key_ders.is_empty() { - // RSA - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::rsa_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - // EC - key_reader = BufReader::new(load_file_buf("certs", "cert.key")?); // Re-read key file - key_ders = rustls_pemfile::ec_private_keys(&mut key_reader) - .map(|r| r.map(Into::into)) - .collect::, io::Error>>()?; - } - - if key_ders.is_empty() { - return Err("No private keys found in key file. (Tried PKCS8, RSA, and EC)".into()); - } - - let mut config = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_ders, key_ders.remove(0))?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - Ok(Some(Arc::new(config))) + STANDARD.encode(result) } From 4becc01f3a41a7efc9c4c58512eea132f4c2fc3a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:37:42 +0100 Subject: [PATCH 059/220] [FIX] Websocket & webserver is now actix, because that should be better than hyper & axum, i guess we'll see in the long run :}, ToDo: IF THIS WORKS: implement on the iota --- Cargo.lock | 1280 ++++++++++++++++++++++++------ Cargo.toml | 16 +- src/main.rs | 5 +- src/server/api.rs | 447 +++++------ src/server/omikron_connection.rs | 39 +- src/server/server.rs | 438 ++-------- src/server/socket.rs | 186 +++-- 7 files changed, 1441 insertions(+), 970 deletions(-) mode change 100644 => 100755 src/server/socket.rs diff --git a/Cargo.lock b/Cargo.lock index ba72ade..7469a5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,8 +6,13 @@ version = 4 name = "Omega" version = "0.1.0" dependencies = [ + "actix", + "actix-rt", + "actix-web", + "actix-web-actors", "aes-gcm", "ansi_term", + "anyhow", "async-trait", "async-tungstenite", "axum", @@ -22,6 +27,7 @@ dependencies = [ "hkdf", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "json", "once_cell", @@ -49,6 +55,265 @@ dependencies = [ "zip", ] +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.11.0", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64 0.22.1", + "bitflags 2.11.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2 0.3.27", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "actix-macros", + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "impl-more", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.2", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.3.1+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -108,6 +373,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -123,6 +403,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + [[package]] name = "arbitrary" version = "1.4.2" @@ -337,7 +623,7 @@ dependencies = [ "tokio-openssl", "tokio-rustls", "tungstenite", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -363,9 +649,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "zeroize", @@ -373,9 +659,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -394,7 +680,7 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -427,7 +713,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "http-body-util", "mime", @@ -467,9 +753,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" @@ -479,9 +765,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -508,6 +794,27 @@ dependencies = [ "piper", ] +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -526,6 +833,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + [[package]] name = "bzip2" version = "0.6.1" @@ -537,9 +853,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.51" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -548,10 +864,16 @@ dependencies = [ ] [[package]] -name = "cfg-expr" -version = "0.20.5" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" dependencies = [ "smallvec", "target-lexicon", @@ -563,6 +885,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cipher" version = "0.4.4" @@ -609,6 +937,16 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -630,6 +968,26 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -689,6 +1047,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -740,9 +1107,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deflate64" @@ -763,9 +1130,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", ] @@ -781,6 +1148,29 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -938,19 +1328,19 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1146,13 +1536,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1162,9 +1554,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", ] [[package]] @@ -1219,7 +1626,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "futures-channel", "futures-core", "futures-executor", @@ -1286,6 +1693,25 @@ dependencies = [ "system-deps", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.13" @@ -1297,7 +1723,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap", "slab", "tokio", @@ -1388,6 +1814,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -1405,7 +1842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1416,7 +1853,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "pin-project-lite", ] @@ -1443,8 +1880,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "http-body", "httparse", "httpdate", @@ -1462,32 +1899,18 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.4.0", "hyper", "hyper-util", + "log", "rustls 0.23.36", + "rustls-native-certs", "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.20" @@ -1498,16 +1921,17 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.4.0", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration", "tokio", + "tower-layer", "tower-service", "tracing", "windows-registry", @@ -1594,6 +2018,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1615,6 +2045,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "indenter" version = "0.3.4" @@ -1623,12 +2059,14 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1691,6 +2129,28 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -1703,9 +2163,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1726,6 +2186,12 @@ dependencies = [ "log", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1735,6 +2201,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libbz2-rs-sys" version = "0.2.2" @@ -1743,15 +2215,15 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.179" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -1759,9 +2231,9 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", - "redox_syscall 0.7.0", + "redox_syscall 0.7.1", ] [[package]] @@ -1774,15 +2246,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-rs-sys" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" -dependencies = [ - "zlib-rs", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1801,6 +2264,23 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.14" @@ -1819,6 +2299,12 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lzma-rust2" version = "0.13.0" @@ -1847,9 +2333,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -1874,23 +2360,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -1903,9 +2390,9 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -1928,9 +2415,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -1968,7 +2455,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2008,7 +2495,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -2030,15 +2517,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-probe" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" @@ -2312,9 +2793,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppmd-rust" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" [[package]] name = "ppv-lite86" @@ -2325,6 +2806,16 @@ 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-macro-crate" version = "3.4.0" @@ -2336,18 +2827,74 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.43" +name = "quinn" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.36", + "socket2 0.6.2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.36", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2376,7 +2923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2396,7 +2943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2411,14 +2958,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -2429,23 +2976,23 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2455,9 +3002,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2465,42 +3012,46 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.8.8" +name = "regex-lite" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", "futures-core", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.36", "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", + "rustls-platform-verifier", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -2518,7 +3069,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2546,9 +3097,24 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "rustix" @@ -2570,7 +3136,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -2599,7 +3165,7 @@ dependencies = [ "log", "once_cell", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] @@ -2610,10 +3176,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.0", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -2636,13 +3202,41 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2655,9 +3249,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -2673,9 +3267,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -2713,24 +3307,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2739,14 +3320,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2887,9 +3474,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2912,9 +3499,19 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -2981,7 +3578,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", ] @@ -3032,7 +3629,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "bytes", "crc", @@ -3061,7 +3658,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3074,7 +3671,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "crc", "dotenvy", @@ -3098,7 +3695,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3122,7 +3719,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", ] @@ -3170,9 +3767,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -3219,7 +3816,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3255,12 +3852,12 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix 1.1.3", "windows-sys 0.61.2", @@ -3277,11 +3874,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -3297,9 +3894,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -3317,22 +3914,34 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", + "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] [[package]] name = "tinystr" @@ -3371,7 +3980,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3449,9 +4058,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", @@ -3485,9 +4094,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow", ] @@ -3500,9 +4109,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -3520,10 +4129,10 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-util", - "http", + "http 1.4.0", "http-body", "iri-string", "pin-project-lite", @@ -3612,7 +4221,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.4.0", "httparse", "log", "native-tls", @@ -3620,7 +4229,7 @@ dependencies = [ "rustls 0.23.36", "rustls-pki-types", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "utf-8", ] @@ -3639,9 +4248,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-normalization" @@ -3658,6 +4267,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -3700,11 +4321,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "wasm-bindgen", ] @@ -3772,9 +4393,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] @@ -3787,9 +4417,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -3800,11 +4430,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -3813,9 +4444,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3823,9 +4454,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -3836,18 +4467,62 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] -name = "web-sys" -version = "0.3.83" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -3863,6 +4538,15 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.22.6" @@ -3874,9 +4558,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -4059,6 +4743,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4095,6 +4788,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4152,6 +4860,12 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4170,6 +4884,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4188,6 +4908,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4218,6 +4944,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4236,6 +4968,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4254,6 +4992,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4272,6 +5016,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4301,9 +5051,91 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -4347,18 +5179,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.32" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.32" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -4468,15 +5300,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" +checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index f89a8a4..d982a18 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] +actix = "0.13.5" +actix-rt = "2.11.0" +actix-web = { version = "4.12.1", features = ["rustls-0_23"] } +actix-web-actors = "4.3.1" aes-gcm = "*" ansi_term = "0.12.1" +anyhow = "1.0.101" async-trait = "0.1.89" async-tungstenite = { version = "0.32.0", features = [ "futures-03-sink", @@ -39,7 +44,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = { version = "0.8.8", features = [ "ws" ] } +axum = { version = "0.8.8", features = ["ws"] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" @@ -50,15 +55,16 @@ futures-util = "0.3.31" hex = "0.4.3" hkdf = "0.12.4" http-body-util = "0.1.3" -hyper = { version = "1.8.1", features = ["full"] } -hyper-util = "0.1.20" +hyper = { version = "1.8.1", features = ["http2", "full"] } +hyper-rustls = { version = "0.27.7", features = ["http2"] } +hyper-util = { version = "0.1.20", features = ["full"] } json = "0.12.4" once_cell = "1.21.3" pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = "0.12.28" -rustls = "0.23.35" +reqwest = "0.13.2" +rustls = "0.23.36" rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" diff --git a/src/main.rs b/src/main.rs index aa6a3f9..88637e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,8 +43,7 @@ async fn main() { } else { log!(" Users"); } - server::server::start(9187, 9188).await; - log!(" API Server on 9187"); - log!(" WS Server on 9188"); + let _ = server::server::start(9187).await; + tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/api.rs b/src/server/api.rs index ae30a24..bc240dc 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,27 +1,21 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; -use crate::util::file_util::load_file_vec; +use crate::util::file_util::load_file; +use crate::{get_public_key, log}; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, }; -use axum::http::HeaderValue; +use actix_web::HttpResponse; +use actix_web::body::BoxBody; +use actix_web::http::StatusCode; use base64::Engine as _; -use http_body_util::Full; -use hyper::body::Bytes; -use hyper::header::{CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_TYPE}; -use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use json::JsonValue; use json::number::Number; -pub async fn handle( - path: &str, - _headers: HeaderMap, - body_string: Option, -) -> HttpResponse> { +pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); let _body: Option = if body_string.is_some() { @@ -33,258 +27,189 @@ pub async fn handle( } else { None }; - let (status, body_text) = if path_parts.len() >= 2 { - match path_parts[1] { - "download" => { - if path_parts.len() == 3 && path_parts[2] == "iota_frontend" { - let file: Bytes = load_file_vec("downloads", "iota_frontend.zip") - .unwrap() - .into(); - let len = file.len(); - let body = Full::new(file); - - let response = HttpResponse::builder() - .status(StatusCode::OK) - .header(CONTENT_TYPE, "application/zip") - .header( - CONTENT_DISPOSITION, - "attachment; filename=\"iota_frontend.zip\"", - ) - .header(CONTENT_LENGTH, len) - .body(body) - .unwrap(); - return response; - } else { - bad_request() - } - } - "get" => match path_parts[2] { - // api/get/omikron -> any omikron - // api/get/omikron/ -> omikron for id (user / iota / omikron) - "omikron" => { - if path_parts.len() == 3 { - if let Ok(omikron_conn) = get_random_omikron().await { - if let Ok((public_key, ip_address)) = - sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_conn.get_omikron_id().await, - public_key, - ip_address - ), - ) - } else { - ( - StatusCode::INTERNAL_SERVER_ERROR, - "selected an invalid omikron".to_string(), - ) - } - } else { - ( - StatusCode::NOT_FOUND, - "couldn't find online omikron".to_string(), - ) - } - } else if path_parts.len() == 4 { - let id = path_parts[3].parse::().unwrap_or(0); - if id == 0 { - not_found() - } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - id, public_key, ip_address - ), - ) - } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address)) = - get_omikron_by_id(omikron_id).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) - } else { - not_found() - } - } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(id).await - { - if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address)) = - get_omikron_by_id(omikron_id).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) - } else { - not_found() - } - } else { - not_found() - } - } else { - not_found() - } - } else { - bad_request() - } - } - // get/id/ - "id" => { - if path_parts.len() != 4 { - bad_request() - } else { - let username = path_parts[3]; - if username.is_empty() { - not_found() - } else { - if let Ok(( - id, - iota_id, - username, - _, - _, - _, - _, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - let cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(id)), - ) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - (StatusCode::OK, cv.to_json().to_string()) - } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) - } - } - } - } - "public_key" => (StatusCode::OK, public_key_to_base64(&get_public_key())), - "user" => { - if path_parts.len() != 4 { - bad_request() - } else { - let id = path_parts[3]; - let id: i64 = id.parse().unwrap_or(0); - if id == 0 { - bad_request() - } else { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - let mut cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(id)), - ) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - if let Some(display) = display { - cv = cv.add_data_str(DataTypes::display, display); - } - if let Some(status) = status { - cv = cv.add_data_str(DataTypes::status, status); - } - if let Some(about) = about { - cv = cv.add_data_str(DataTypes::about, about); - } - if let Some(avatar) = avatar { - cv = cv.add_data_str( - DataTypes::avatar, - base64::engine::general_purpose::STANDARD.encode(avatar), - ); - } - (StatusCode::OK, cv.to_json().to_string()) - } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) - } - } - } - } - _ => { - let id = path_parts[2]; - let id: i64 = id.parse().unwrap_or(0); - if id == 0 { - bad_request() - } else { - bad_request() - } - } - }, - _ => not_found(), + log!("Path parts: {:?}", path_parts); + let (status, body_text) = match path_parts.as_slice() { + ["api", "download", "iota_frontend"] => { + let file = load_file("downloads", "iota_frontend.zip"); + (StatusCode::OK, file) } - } else { - not_found() + ["api", "get", "omikron"] => { + if let Ok(omikron_conn) = get_random_omikron().await { + if let Ok((public_key, ip_address)) = + sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await + { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_conn.get_omikron_id().await, + public_key, + ip_address + ), + ) + } else { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "selected an invalid omikron".to_string(), + ) + } + } else { + ( + StatusCode::NOT_FOUND, + "couldn't find online omikron".to_string(), + ) + } + } + ["api", "get", "omikron", id] => { + let id = id.parse::().unwrap_or(0); + if id == 0 { + not_found() + } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + id, public_key, ip_address + ), + ) + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await + { + if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else { + not_found() + } + } else { + not_found() + } + } + ["api", "get", "id", username] => { + if username.is_empty() { + not_found() + } else if let Ok(( + id, + iota_id, + username, + _, + _, + _, + _, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + let cv = CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); + (StatusCode::OK, cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } + ["api", "get", "public_key"] => (StatusCode::OK, public_key_to_base64(&get_public_key())), + ["api", "get", "user", id] => { + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + let mut cv = CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); + if let Some(display) = display { + cv = cv.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + cv = cv.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + cv = cv.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + cv = cv.add_data_str( + DataTypes::avatar, + base64::engine::general_purpose::STANDARD.encode(avatar), + ); + } + (StatusCode::OK, cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } + _ => ( + StatusCode::INTERNAL_SERVER_ERROR, + CommunicationValue::new(CommunicationType::error) + .to_json() + .to_string(), + ), }; - let body = Full::new(Bytes::from(body_text.to_string())); - HttpResponse::builder().status(status).body(body).unwrap() + let body_bytes = body_text.to_string().into_bytes(); + let body = BoxBody::new(body_bytes.clone()); + HttpResponse::new(status).set_body(body) } + pub fn bad_request() -> (StatusCode, String) { (StatusCode::BAD_REQUEST, "400 Bad Request".to_string()) } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 3eeb22b..b44d1b4 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,19 +1,16 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::server::omikron_manager; use crate::server::short_link::add_short_link; +use crate::server::socket::{WsSendMessage, WsSession}; use crate::sql::connection_status::UserStatus; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_err, log_in, log_out}; -use axum::extract::ws::WebSocket; -use axum::extract::ws::{Message, Utf8Bytes}; +use crate::{get_private_key, get_public_key, log_in, log_out}; +use actix::Addr; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use futures::stream::SplitSink; -use futures::stream::SplitStream; -use futures_util::SinkExt; use json::JsonValue; use json::number::Number; use rand::Rng; @@ -24,8 +21,7 @@ use uuid::Uuid; use x448::PublicKey; pub struct OmikronConnection { - pub sender: Arc>>, - pub receiver: Arc>>, + pub ws_addr: Arc>>, pub omikron_id: Arc>, pub pub_key: Arc>>>, identified: Arc>, @@ -39,13 +35,9 @@ pub struct OmikronConnection { } impl OmikronConnection { - pub fn new( - sender: SplitSink, - receiver: SplitStream, - ) -> Arc { + pub fn new(ws_addr: Addr) -> Arc { Arc::new(Self { - sender: Arc::new(RwLock::new(sender)), - receiver: Arc::new(RwLock::new(receiver)), + ws_addr: Arc::new(RwLock::new(ws_addr)), omikron_id: Arc::new(RwLock::new(0)), pub_key: Arc::new(RwLock::new(None)), identified: Arc::new(RwLock::new(false)), @@ -56,25 +48,20 @@ impl OmikronConnection { }) } pub async fn send_message(&self, cv: &CommunicationValue) { - let mut sender = self.sender.write().await; - let message_text = Message::Text(Utf8Bytes::from(cv.to_json().to_string())); + let text = cv.to_json().to_string(); + if !cv.is_type(CommunicationType::pong) { log_out!( *self.omikron_id.read().await, PrintType::Omikron, "{}", - cv.to_json().to_string() - ); - } - if let Err(e) = sender.send(message_text).await { - log_err!( - *self.omikron_id.read().await, - PrintType::Omikron, - "WebSocket send error: {}", - e + text ); } + + self.ws_addr.read().await.do_send(WsSendMessage(text)); } + pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await } @@ -948,7 +935,6 @@ impl OmikronConnection { self.send_message(&error).await; } pub async fn close(&self) { - let mut sender = self.sender.write().await; if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { @@ -957,7 +943,6 @@ impl OmikronConnection { user_online_tracker::untrack_omikron(omikron_id).await; } } - let _ = sender.close().await; } pub async fn handle_close(self: Arc) { if self.is_identified().await { diff --git a/src/server/server.rs b/src/server/server.rs index afa0244..5167444 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,389 +1,83 @@ -<<<<<<< HEAD -use base64::Engine; -use base64::engine::general_purpose::STANDARD; -use bytes::Bytes; -use futures_util::TryFutureExt; -use http_body_util::BodyExt; -use http_body_util::Full; -use hyper::server::conn::{http1, http2}; -use hyper::{Method, StatusCode}; -use hyper::{Request as HttpRequest, Response as HttpResponse, body::Incoming, upgrade}; -use hyper_util::rt::TokioExecutor; -use hyper_util::rt::tokio::TokioIo; -use hyper_util::service::TowerToHyperService; -use sha1::{Digest, Sha1}; -use std::io; -use std::net::SocketAddr; -use std::result::Result::Ok; -use std::{future::Future, pin::Pin, time::Duration}; -use tokio::net::TcpListener; -use tower::Service; -======= -use axum::{ - Router, - body::Body, - extract::{ConnectInfo, OriginalUri, Path, ws::WebSocketUpgrade}, - response::{IntoResponse, Redirect}, - routing::get, +use crate::{ + log, + server::{api, short_link::get_short_link, socket}, + util::file_util::get_directory, }; -use pnet::datalink::NetworkInterface; -use std::net::SocketAddr; -use std::time::Duration; -use tokio::net::TcpListener; ->>>>>>> 7f78c8669b36cbe39755d69cccd6971e56e10290 +use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; +use actix_web_actors::ws; -use crate::log; -use crate::server::api; -use crate::server::short_link::get_short_link; -use crate::server::socket; +use rustls::ServerConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls_pemfile::{certs, pkcs8_private_keys}; -<<<<<<< HEAD -// --- ApiService for HTTP/2 --- +use std::fs::File; +use std::io::BufReader; -#[derive(Clone)] -struct ApiService { - _peer_addr: SocketAddr, -} +pub async fn start(port: u16) -> anyhow::Result<()> { + let mut cert_reader = + BufReader::new(File::open(format!("{}/certs/cert.pem", get_directory()))?); -impl Service> for ApiService { - type Response = HttpResponse>; - type Error = io::Error; - type Future = Pin> + Send>>; -======= -pub async fn start(port: u16) -> bool { - let app = Router::new() - .route("/ws/omikron", get(ws_handler)) - .route("/direct/{short}", get(direct_handler)) - .fallback(fallback_handler); - run_http_server(port, app).await -} + let mut key_reader = BufReader::new(File::open(format!("{}/certs/key.pem", get_directory()))?); -async fn ws_handler( - ws: WebSocketUpgrade, - OriginalUri(uri): OriginalUri, - ConnectInfo(_): ConnectInfo, -) -> impl IntoResponse { - log!("Attempting WebSocket upgrade on {}", uri.path()); - let path = uri.path().to_string(); ->>>>>>> 7f78c8669b36cbe39755d69cccd6971e56e10290 + let cert_chain: Vec> = + certs(&mut cert_reader).collect::>()?; - ws.on_upgrade(async move |socket| socket::handle(path, socket)) -} + let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) + .map(|res| res.map(Into::into)) + .collect::>()?; -<<<<<<< HEAD - fn call(&mut self, req: HttpRequest) -> Self::Future { - let (parts, body) = req.into_parts(); - let path = parts.uri.path().to_string(); - let headers = parts.headers.clone(); + let key = keys.remove(0); - let fut = async move { - if path.starts_with("/api") { - let whole_body = - tokio::time::timeout(Duration::from_secs(10), body.collect()).await??; - let bytes = whole_body.to_bytes(); - let body_string: Option = String::from_utf8(bytes.to_vec()).ok(); - Ok(api::handle(&path, headers, body_string).await) - } else if path.starts_with("/direct") { - let short = path.replace("/direct/", ""); - if let Ok(long) = get_short_link(&short).await { - let response = HttpResponse::builder() - .status(StatusCode::FOUND) - .header("Location", long) - .body(Full::new(Bytes::from(""))) - .unwrap(); - Ok(response) - } else { - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from("Short link not found"))) - .unwrap(); - Ok(response) - } - } else { - // Not a WebSocket, /api, or /direct, so it's a 404 - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from("Not Found"))) - .unwrap(); - Ok(response) - } - }; + let mut config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key)?; - Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { - io::Error::new( - io::ErrorKind::Other, - format!("Error in API request: {}", err), - ) - })) - } -} + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; -// --- WebSocketService for HTTP/1.1 --- + let addr = format!("0.0.0.0:{port}"); + log!(" Server on {}", addr); -#[derive(Clone)] -struct WebSocketService { - _peer_addr: SocketAddr, -} - -impl Service> for WebSocketService { - type Response = HttpResponse>; - type Error = io::Error; - type Future = Pin> + Send>>; - - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(std::io::Result::Ok(())) - } - - fn call(&mut self, req: HttpRequest) -> Self::Future { - let (parts, body) = req.into_parts(); - let method = parts.method.clone(); - let path = parts.uri.path().to_string(); - let headers = parts.headers.clone(); - - let fut = async move { - let is_websocket_upgrade = path.starts_with("/ws") - && method == Method::GET - && headers - .get("connection") - .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) - .unwrap_or(false) - && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); - - if is_websocket_upgrade { - log!("Attempting WebSocket upgrade on /ws"); - if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { - let sec_websocket_key_str = - sec_websocket_key.to_str().unwrap_or("").to_string(); - let sec_websocket_accept = calculate_accept_key(&sec_websocket_key_str); - - let response = HttpResponse::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header("Upgrade", "websocket") - .header("Connection", "Upgrade") - .header("Sec-WebSocket-Accept", sec_websocket_accept) - .body(Full::new(Bytes::from(""))) - .unwrap(); - - let req_for_upgrade = HttpRequest::from_parts(parts, body); - let upgrades = upgrade::on(req_for_upgrade); - socket::handle(path, upgrades); - Ok(response) - } else { - log!("No Sec-WebSocket-Key found in request headers"); - let response = HttpResponse::builder() - .status(StatusCode::BAD_REQUEST) - .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) - .unwrap(); - Ok(response) - } - } else { - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from( - "Not Found: Use the API server for non-WebSocket requests.", - ))) - .unwrap(); - Ok(response) - } - }; - - Box::pin(fut.map_err(|_err: color_eyre::eyre::ErrReport| { - io::Error::new(io::ErrorKind::Other, "Error in WebSocket request") - })) - } -} - -async fn run_api_server(port: u16) -> bool { - let mut ip = "0.0.0.0".to_string(); - for iface in pnet::datalink::interfaces() { - if let Some(ip_net) = iface.ips.iter().find(|ip_net| { - ip_net.is_ipv4() - && (ip_net.ip().to_string().starts_with("10.") - || ip_net.ip().to_string().starts_with("192.")) - }) { - ip = ip_net.ip().to_string(); - break; - } - } - - let addr = SocketAddr::new(ip.parse().unwrap(), port); - let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener_res { - log!( - "[FATAL] Failed to bind API server to port {}: {:?}", - port, - e - ); - return false; - } - let listener = listener_res.unwrap(); - log!("API Server (HTTP/2) listening on {}:{}", ip, port); - - tokio::spawn(async move { - loop { - if let Ok((stream, addr)) = listener.accept().await { - let service = ApiService { _peer_addr: addr }; - let io = TokioIo::new(stream); - tokio::spawn(async move { - let conn = http2::Builder::new(TokioExecutor::new()) - .serve_connection(io, TowerToHyperService::new(service)); - if let Err(err) = conn.await { - if !err - .to_string() - .starts_with("error shutting down connection") - { - log!("API server connection error: {:?}", err); - } - } - }); - } - } - }); - - true -} - -async fn run_websocket_server(port: u16) -> bool { - let mut ip = "0.0.0.0".to_string(); - for iface in pnet::datalink::interfaces() { - if let Some(ip_net) = iface.ips.iter().find(|ip_net| { - ip_net.is_ipv4() - && (ip_net.ip().to_string().starts_with("10.") - || ip_net.ip().to_string().starts_with("192.")) - }) { - ip = ip_net.ip().to_string(); - break; - } - } - - let addr = SocketAddr::new(ip.parse().unwrap(), port); - let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener_res { - log!( - "[FATAL] Failed to bind WebSocket server to port {}: {:?}", - port, - e - ); - return false; - } - let listener = listener_res.unwrap(); - log!("WebSocket Server (HTTP/1.1) listening on {}:{}", ip, port); - - tokio::spawn(async move { - loop { - if let Ok((stream, addr)) = listener.accept().await { - let service = WebSocketService { _peer_addr: addr }; - let io = TokioIo::new(stream); - tokio::spawn(async move { - let conn = http1::Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .serve_connection(io, TowerToHyperService::new(service)) - .with_upgrades(); - if let Err(err) = conn.await { - if !err - .to_string() - .starts_with("error shutting down connection") - { - log!("WebSocket server connection error: {:?}", err); - } - } - }); - } - } - }); - - true -} - -// --- Main Start function --- - -pub async fn start(api_port: u16, ws_port: u16) { - // We are not using TLS for this setup as per the request's focus on splitting protocols. - // The previous TLS loading logic is removed for simplicity. - tokio::spawn(run_api_server(api_port)); - tokio::spawn(run_websocket_server(ws_port)); -} - -fn calculate_accept_key(key: &str) -> String { - let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(key.as_bytes()); - sha1.update(websocket_guid.as_bytes()); - let result = sha1.finalize(); - STANDARD.encode(result) -======= -async fn direct_handler(Path(short): Path) -> impl IntoResponse { - match get_short_link(&short).await { - Ok(long) => Redirect::temporary(&long), - Err(_) => Redirect::temporary("https://tensamin.net"), - } -} - -async fn fallback_handler( - OriginalUri(uri): OriginalUri, - headers: axum::http::HeaderMap, - body: Body, -) -> impl IntoResponse { - let path = uri.path().to_string(); - - let whole_body = tokio::time::timeout( - Duration::from_secs(10), - axum::body::to_bytes(body, 1024 * 1024 * 10), - ) - .await; - - let body_string = match whole_body { - Ok(Ok(bytes)) => String::from_utf8(bytes.to_vec()).ok(), - _ => None, - }; - - api::handle(&path, headers, body_string).await -} - -async fn run_http_server(port: u16, app: Router) -> bool { - let ip = find_local_ip(); - let listener = match TcpListener::bind(format!("0.0.0.0:{}", port)).await { - Ok(l) => l, - Err(e) => { - log!("Failed to bind to port {}: {:?}", port, e); - return false; - } - }; - - log!( - "Standard Server listening for HTTP and WS on {}:{}", - ip, - port - ); - - axum::serve( - listener, - app.into_make_service_with_connect_info::(), - ) - .await - .map(|_| true) - .unwrap_or_else(|e| { - log!("Server error: {}", e); - false + HttpServer::new(move || { + App::new() + .route("/api/{path:.*}", web::to(api_handler)) + .route("/direct/{path:.*}", web::to(direct_handler)) + .route("/ws/{path:.*}", web::get().to(ws_handler)) }) + .bind_rustls_0_23(addr, config)? + .run() + .await?; + + Ok(()) +} +async fn direct_handler(req: HttpRequest) -> impl Responder { + let path = req.uri().path().to_string(); + let short = path.replace("/direct/", ""); + + if let Ok(long) = get_short_link(&short).await { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, long)) + .finish() + } else { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, "https://tensamin.net")) + .finish() + } +} +async fn ws_handler( + req: HttpRequest, + stream: web::Payload, + path: web::Path, +) -> Result { + let path = path.into_inner(); + println!("WS handler reached: {}", path); + + ws::start(socket::WsSession::new(path), &req, stream) } -fn find_local_ip() -> String { - for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - if !iface.ips.is_empty() { - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - if ips.starts_with("10.") || ips.starts_with("192.") { - return ips.to_string(); - } - } - } - "0.0.0.0".to_string() ->>>>>>> 7f78c8669b36cbe39755d69cccd6971e56e10290 +async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { + let path = req.uri().path().to_string(); + let body_string = String::from_utf8_lossy(&body).to_string(); + + api::handle(&path, Some(body_string)).await } diff --git a/src/server/socket.rs b/src/server/socket.rs old mode 100644 new mode 100755 index 25a00f7..7d04b26 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,99 +1,129 @@ use std::sync::Arc; +use std::time::{Duration, Instant}; -use axum::extract::ws::{Message, Utf8Bytes, WebSocket}; -use futures::StreamExt; +use actix::{Actor, ActorContext, AsyncContext, StreamHandler}; +use actix_web_actors::ws; use crate::data::communication::{CommunicationType, CommunicationValue}; use crate::log; use crate::server::omikron_connection::OmikronConnection; -pub fn handle(path: String, upgrades: WebSocket) { - tokio::spawn(async move { - log!( - "[ws] Spawning new task to handle WebSocket upgrade for path: {}", - path - ); - log!("[ws] WebSocket upgrade successful for path: {}", path); +const IDLE_TIMEOUT: Duration = Duration::from_secs(30); - log!( - "[ws] WebSocket handshake successful, handling connection for {}", - path - ); +use actix::Message; - let (writer, reader) = upgrades.split(); - if path == "/ws/omikron" { - let connection = OmikronConnection::new(writer, reader); - tokio::spawn(start_connecteable_handler(connection)); - } +#[derive(Message)] +#[rtype(result = "()")] +pub struct WsSendMessage(pub String); - log!( - "[ws] WebSocket handling task for path: {} is finished.", - path - ); - }); +impl actix::Handler for WsSession { + type Result = (); + + fn handle(&mut self, msg: WsSendMessage, ctx: &mut Self::Context) { + ctx.text(msg.0); + } } -pub async fn start_connecteable_handler(connection: Arc) { - use futures::SinkExt; - use tokio::time::Duration; - const IDLE_TIMEOUT: Duration = Duration::from_secs(30); +pub struct WsSession { + path: String, + last_heartbeat: Instant, + omikron: Option>, +} - log!("[ws_handler] Starting connection handler loop."); - loop { - let mut receiver_guard = connection.receiver.write().await; - - match tokio::time::timeout(IDLE_TIMEOUT, receiver_guard.next()).await { - Ok(Some(Ok(msg))) => { - drop(receiver_guard); - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Close(_) => { - log!("[ws_handler] Received 'Close' message. Breaking loop."); - break; - } - Message::Pong(_) => { - log!("[ws_handler] Received 'Pong'. Connection is alive."); - } - _ => { - log!("[ws_handler] Received unhandled message type."); - } +impl WsSession { + pub fn new(path: String) -> Self { + Self { + path, + last_heartbeat: Instant::now(), + omikron: None, + } + } + + fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext) { + ctx.run_interval(Duration::from_secs(5), |act, ctx| { + if Instant::now().duration_since(act.last_heartbeat) > IDLE_TIMEOUT { + log!("[ws_handler] Heartbeat failed. Disconnecting."); + ctx.close(None); + ctx.stop(); + return; + } + + let ping = CommunicationValue::new(CommunicationType::ping) + .to_json() + .to_string(); + + ctx.text(ping); + }); + } +} + +impl Actor for WsSession { + type Context = ws::WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + self.start_heartbeat(ctx); + + if self.path == "omikron" { + let addr = ctx.address(); + let connection = OmikronConnection::new(addr); + self.omikron = Some(connection); + } + } + + fn stopped(&mut self, _: &mut Self::Context) { + log!( + "[ws] WebSocket handling task for path: {} is finished.", + self.path + ); + + if let Some(conn) = &self.omikron { + let conn = conn.clone(); + actix_rt::spawn(async move { + conn.handle_close().await; + }); + } + } +} +impl StreamHandler> for WsSession { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + match msg { + Ok(ws::Message::Text(text)) => { + self.last_heartbeat = Instant::now(); + + if let Some(conn) = &self.omikron { + let conn_clone = conn.clone(); + actix_rt::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); } } - Ok(Some(Err(e))) => { - log!("[ERROR] WS Error: {}. Breaking loop.", e); - break; + + Ok(ws::Message::Ping(msg)) => { + self.last_heartbeat = Instant::now(); + ctx.pong(&msg); } - Ok(_) => { - log!("[ws_handler] WebSocket stream closed by peer. Breaking loop."); - break; + + Ok(ws::Message::Pong(_)) => { + self.last_heartbeat = Instant::now(); + log!("[ws_handler] Received Pong. Connection alive."); } - Err(_) => { - drop(receiver_guard); - log!("[ws_handler] Timeout: Dropped receiver lock. Sending a ping."); - let mut sender = connection.sender.write().await; - log!("[ws_handler] Acquired sender lock for ping."); - if let Err(e) = sender - .send(Message::Text(Utf8Bytes::from( - CommunicationValue::new(CommunicationType::ping) - .to_json() - .to_string(), - ))) - .await - { - log!("[ERROR] Failed to send ping: {}. Closing connection.", e); - break; - } - log!("[ws_handler] Ping sent successfully."); + + Ok(ws::Message::Close(reason)) => { + log!("[ws_handler] Received Close. Disconnecting."); + ctx.close(reason); + ctx.stop(); + } + + Ok(ws::Message::Binary(_)) => { + log!("[ws_handler] Binary message ignored."); + } + + Err(e) => { + log!("[ERROR] WS Error: {}. Closing.", e); + ctx.stop(); } + + _ => {} } } - log!("[ws_handler] Connection handler loop finished."); - connection.handle_close().await; - log!("[ws_handler] Connection closed."); } From cb7579d86dcb88ca6fc6408ae91ad6cad9503b5c Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 15 Feb 2026 00:37:42 +0100 Subject: [PATCH 060/220] [FIX] Websocket & webserver is now actix, because that should be better than hyper & axum, i guess we'll see in the long run :}, ToDo: IF THIS WORKS: implement on the iota --- Cargo.lock | 1280 ++++++++++++++++++++++++------ Cargo.toml | 16 +- src/main.rs | 5 +- src/server/api.rs | 447 +++++------ src/server/omikron_connection.rs | 39 +- src/server/server.rs | 438 ++-------- src/server/socket.rs | 186 +++-- 7 files changed, 1441 insertions(+), 970 deletions(-) mode change 100644 => 100755 src/server/socket.rs diff --git a/Cargo.lock b/Cargo.lock index ba72ade..7469a5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,8 +6,13 @@ version = 4 name = "Omega" version = "0.1.0" dependencies = [ + "actix", + "actix-rt", + "actix-web", + "actix-web-actors", "aes-gcm", "ansi_term", + "anyhow", "async-trait", "async-tungstenite", "axum", @@ -22,6 +27,7 @@ dependencies = [ "hkdf", "http-body-util", "hyper", + "hyper-rustls", "hyper-util", "json", "once_cell", @@ -49,6 +55,265 @@ dependencies = [ "zip", ] +[[package]] +name = "actix" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" +dependencies = [ + "actix-macros", + "actix-rt", + "actix_derive", + "bitflags 2.11.0", + "bytes", + "crossbeam-channel", + "futures-core", + "futures-sink", + "futures-task", + "futures-util", + "log", + "once_cell", + "parking_lot", + "pin-project-lite", + "smallvec", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64 0.22.1", + "bitflags 2.11.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2 0.3.27", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "actix-macros", + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "impl-more", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.2", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-actors" +version = "4.3.1+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" +dependencies = [ + "actix", + "actix-codec", + "actix-http", + "actix-web", + "bytes", + "bytestring", + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "actix_derive" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "addr2line" version = "0.25.1" @@ -108,6 +373,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -123,6 +403,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "anyhow" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" + [[package]] name = "arbitrary" version = "1.4.2" @@ -337,7 +623,7 @@ dependencies = [ "tokio-openssl", "tokio-rustls", "tungstenite", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -363,9 +649,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.2" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88aab2464f1f25453baa7a07c84c5b7684e274054ba06817f382357f77a288" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "zeroize", @@ -373,9 +659,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.35.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45afffdee1e7c9126814751f88dddc747f41d91da16c9551a0f1e8a11e788a1" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -394,7 +680,7 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http", + "http 1.4.0", "http-body", "http-body-util", "hyper", @@ -427,7 +713,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "http-body-util", "mime", @@ -467,9 +753,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d809780667f4410e7c41b07f52439b94d2bdf8528eeedc287fa38d3b7f95d82" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" @@ -479,9 +765,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -508,6 +794,27 @@ dependencies = [ "piper", ] +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.19.1" @@ -526,6 +833,15 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + [[package]] name = "bzip2" version = "0.6.1" @@ -537,9 +853,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.51" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0aeaff4ff1a90589618835a598e545176939b97874f7abc7851caa0618f203" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -548,10 +864,16 @@ dependencies = [ ] [[package]] -name = "cfg-expr" -version = "0.20.5" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21be0e1ce6cdb2ee7fff840f922fb04ead349e5cfb1e750b769132d44ce04720" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-expr" +version = "0.20.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" dependencies = [ "smallvec", "target-lexicon", @@ -563,6 +885,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "cipher" version = "0.4.4" @@ -609,6 +937,16 @@ dependencies = [ "tracing-error", ] +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -630,6 +968,26 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -689,6 +1047,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -740,9 +1107,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deflate64" @@ -763,9 +1130,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", ] @@ -781,6 +1148,29 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + [[package]] name = "digest" version = "0.10.7" @@ -938,19 +1328,19 @@ checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "find-msvc-tools" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645cbb3a84e60b7531617d5ae4e57f7e27308f6445f5abf653209ea76dec8dff" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.1.5" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", - "libz-rs-sys", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -1146,13 +1536,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1162,9 +1554,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi", "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", ] [[package]] @@ -1219,7 +1626,7 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "futures-channel", "futures-core", "futures-executor", @@ -1286,6 +1693,25 @@ dependencies = [ "system-deps", ] +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "h2" version = "0.4.13" @@ -1297,7 +1723,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.4.0", "indexmap", "slab", "tokio", @@ -1388,6 +1814,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.4.0" @@ -1405,7 +1842,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.0", ] [[package]] @@ -1416,7 +1853,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.0", "http-body", "pin-project-lite", ] @@ -1443,8 +1880,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "http-body", "httparse", "httpdate", @@ -1462,32 +1899,18 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http", + "http 1.4.0", "hyper", "hyper-util", + "log", "rustls 0.23.36", + "rustls-native-certs", "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.20" @@ -1498,16 +1921,17 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.4.0", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration", "tokio", + "tower-layer", "tower-service", "tracing", "windows-registry", @@ -1594,6 +2018,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "idna" version = "1.1.0" @@ -1615,6 +2045,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + [[package]] name = "indenter" version = "0.3.4" @@ -1623,12 +2059,14 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", + "serde", + "serde_core", ] [[package]] @@ -1691,6 +2129,28 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.34" @@ -1703,9 +2163,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -1726,6 +2186,12 @@ dependencies = [ "log", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.5.0" @@ -1735,6 +2201,12 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libbz2-rs-sys" version = "0.2.2" @@ -1743,15 +2215,15 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.179" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -1759,9 +2231,9 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "libc", - "redox_syscall 0.7.0", + "redox_syscall 0.7.1", ] [[package]] @@ -1774,15 +2246,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "libz-rs-sys" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c10501e7805cee23da17c7790e59df2870c0d4043ec6d03f67d31e2b53e77415" -dependencies = [ - "zlib-rs", -] - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1801,6 +2264,23 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + [[package]] name = "lock_api" version = "0.4.14" @@ -1819,6 +2299,12 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + [[package]] name = "lzma-rust2" version = "0.13.0" @@ -1847,9 +2333,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -1874,23 +2360,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" dependencies = [ "libc", "log", "openssl", - "openssl-probe 0.1.6", + "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] @@ -1903,9 +2390,9 @@ checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" [[package]] name = "ntapi" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c70f219e21142367c70c0b30c6a9e3a14d55b4d12a204d897fbec83a0363f081" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -1928,9 +2415,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -1968,7 +2455,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] @@ -2008,7 +2495,7 @@ version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "cfg-if", "foreign-types", "libc", @@ -2030,15 +2517,9 @@ dependencies = [ [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" - -[[package]] -name = "openssl-probe" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" @@ -2312,9 +2793,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppmd-rust" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d558c559f0450f16f2a27a1f017ef38468c1090c9ce63c8e51366232d53717b4" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" [[package]] name = "ppv-lite86" @@ -2325,6 +2806,16 @@ 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-macro-crate" version = "3.4.0" @@ -2336,18 +2827,74 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.43" +name = "quinn" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls 0.23.36", + "socket2 0.6.2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls 0.23.36", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2376,7 +2923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2396,7 +2943,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -2411,14 +2958,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -2429,23 +2976,23 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "redox_syscall" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", ] [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -2455,9 +3002,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2465,42 +3012,46 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.8.8" +name = "regex-lite" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", "encoding_rs", "futures-core", - "h2", - "http", + "h2 0.4.13", + "http 1.4.0", "http-body", "http-body-util", "hyper", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls 0.23.36", "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", + "rustls-platform-verifier", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -2518,7 +3069,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -2546,9 +3097,24 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "rustix" @@ -2570,7 +3136,7 @@ version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "errno", "libc", "linux-raw-sys 0.11.0", @@ -2599,7 +3165,7 @@ dependencies = [ "log", "once_cell", "rustls-pki-types", - "rustls-webpki 0.103.8", + "rustls-webpki 0.103.9", "subtle", "zeroize", ] @@ -2610,10 +3176,10 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.0", + "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] @@ -2636,13 +3202,41 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ + "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.36", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.9", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2655,9 +3249,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -2673,9 +3267,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -2713,24 +3307,11 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.11.1" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" dependencies = [ - "bitflags 2.10.0", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" -dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2739,14 +3320,20 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" dependencies = [ "core-foundation-sys", "libc", ] +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + [[package]] name = "serde" version = "1.0.228" @@ -2887,9 +3474,9 @@ checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" @@ -2912,9 +3499,19 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -2981,7 +3578,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", ] @@ -3032,7 +3629,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "bytes", "crc", @@ -3061,7 +3658,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3074,7 +3671,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags 2.11.0", "byteorder", "crc", "dotenvy", @@ -3098,7 +3695,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3122,7 +3719,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", ] @@ -3170,9 +3767,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -3219,7 +3816,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3255,12 +3852,12 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.24.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix 1.1.3", "windows-sys 0.61.2", @@ -3277,11 +3874,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -3297,9 +3894,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -3317,22 +3914,34 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", + "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] [[package]] name = "tinystr" @@ -3371,7 +3980,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3449,9 +4058,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.9.10+spec-1.1.0" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0825052159284a1a8b4d6c0c86cbc801f2da5afd2b225fa548c72f2e74002f48" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ "indexmap", "serde_core", @@ -3485,9 +4094,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.0.8+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" dependencies = [ "winnow", ] @@ -3500,9 +4109,9 @@ checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -3520,10 +4129,10 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.10.0", + "bitflags 2.11.0", "bytes", "futures-util", - "http", + "http 1.4.0", "http-body", "iri-string", "pin-project-lite", @@ -3612,7 +4221,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http", + "http 1.4.0", "httparse", "log", "native-tls", @@ -3620,7 +4229,7 @@ dependencies = [ "rustls 0.23.36", "rustls-pki-types", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "utf-8", ] @@ -3639,9 +4248,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "unicode-normalization" @@ -3658,6 +4267,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + [[package]] name = "universal-hash" version = "0.5.1" @@ -3700,11 +4321,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "wasm-bindgen", ] @@ -3772,9 +4393,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] @@ -3787,9 +4417,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -3800,11 +4430,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -3813,9 +4444,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3823,9 +4454,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -3836,18 +4467,62 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] -name = "web-sys" -version = "0.3.83" +name = "wasm-encoder" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -3863,6 +4538,15 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.22.6" @@ -3874,9 +4558,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -4059,6 +4743,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4095,6 +4788,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4152,6 +4860,12 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4170,6 +4884,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4188,6 +4908,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4218,6 +4944,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4236,6 +4968,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4254,6 +4992,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4272,6 +5016,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4301,9 +5051,91 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -4347,18 +5179,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.32" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fabae64378cb18147bb18bca364e63bdbe72a0ffe4adf0addfec8aa166b2c56" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.32" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9c2d862265a8bb4471d87e033e730f536e2a285cc7cb05dbce09a2a97075f90" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -4468,15 +5300,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" +checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" [[package]] name = "zmij" -version = "1.0.12" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index f89a8a4..d982a18 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] +actix = "0.13.5" +actix-rt = "2.11.0" +actix-web = { version = "4.12.1", features = ["rustls-0_23"] } +actix-web-actors = "4.3.1" aes-gcm = "*" ansi_term = "0.12.1" +anyhow = "1.0.101" async-trait = "0.1.89" async-tungstenite = { version = "0.32.0", features = [ "futures-03-sink", @@ -39,7 +44,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = { version = "0.8.8", features = [ "ws" ] } +axum = { version = "0.8.8", features = ["ws"] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" @@ -50,15 +55,16 @@ futures-util = "0.3.31" hex = "0.4.3" hkdf = "0.12.4" http-body-util = "0.1.3" -hyper = { version = "1.8.1", features = ["full"] } -hyper-util = "0.1.20" +hyper = { version = "1.8.1", features = ["http2", "full"] } +hyper-rustls = { version = "0.27.7", features = ["http2"] } +hyper-util = { version = "0.1.20", features = ["full"] } json = "0.12.4" once_cell = "1.21.3" pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = "0.12.28" -rustls = "0.23.35" +reqwest = "0.13.2" +rustls = "0.23.36" rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" diff --git a/src/main.rs b/src/main.rs index aa6a3f9..88637e6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,8 +43,7 @@ async fn main() { } else { log!(" Users"); } - server::server::start(9187, 9188).await; - log!(" API Server on 9187"); - log!(" WS Server on 9188"); + let _ = server::server::start(9187).await; + tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/api.rs b/src/server/api.rs index ae30a24..bc240dc 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,27 +1,21 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; -use crate::util::file_util::load_file_vec; +use crate::util::file_util::load_file; +use crate::{get_public_key, log}; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, }; -use axum::http::HeaderValue; +use actix_web::HttpResponse; +use actix_web::body::BoxBody; +use actix_web::http::StatusCode; use base64::Engine as _; -use http_body_util::Full; -use hyper::body::Bytes; -use hyper::header::{CONTENT_DISPOSITION, CONTENT_LENGTH, CONTENT_TYPE}; -use hyper::{HeaderMap, Response as HttpResponse, StatusCode}; use json::JsonValue; use json::number::Number; -pub async fn handle( - path: &str, - _headers: HeaderMap, - body_string: Option, -) -> HttpResponse> { +pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); let _body: Option = if body_string.is_some() { @@ -33,258 +27,189 @@ pub async fn handle( } else { None }; - let (status, body_text) = if path_parts.len() >= 2 { - match path_parts[1] { - "download" => { - if path_parts.len() == 3 && path_parts[2] == "iota_frontend" { - let file: Bytes = load_file_vec("downloads", "iota_frontend.zip") - .unwrap() - .into(); - let len = file.len(); - let body = Full::new(file); - - let response = HttpResponse::builder() - .status(StatusCode::OK) - .header(CONTENT_TYPE, "application/zip") - .header( - CONTENT_DISPOSITION, - "attachment; filename=\"iota_frontend.zip\"", - ) - .header(CONTENT_LENGTH, len) - .body(body) - .unwrap(); - return response; - } else { - bad_request() - } - } - "get" => match path_parts[2] { - // api/get/omikron -> any omikron - // api/get/omikron/ -> omikron for id (user / iota / omikron) - "omikron" => { - if path_parts.len() == 3 { - if let Ok(omikron_conn) = get_random_omikron().await { - if let Ok((public_key, ip_address)) = - sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_conn.get_omikron_id().await, - public_key, - ip_address - ), - ) - } else { - ( - StatusCode::INTERNAL_SERVER_ERROR, - "selected an invalid omikron".to_string(), - ) - } - } else { - ( - StatusCode::NOT_FOUND, - "couldn't find online omikron".to_string(), - ) - } - } else if path_parts.len() == 4 { - let id = path_parts[3].parse::().unwrap_or(0); - if id == 0 { - not_found() - } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - id, public_key, ip_address - ), - ) - } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address)) = - get_omikron_by_id(omikron_id).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) - } else { - not_found() - } - } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(id).await - { - if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address)) = - get_omikron_by_id(omikron_id).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) - } else { - not_found() - } - } else { - not_found() - } - } else { - not_found() - } - } else { - bad_request() - } - } - // get/id/ - "id" => { - if path_parts.len() != 4 { - bad_request() - } else { - let username = path_parts[3]; - if username.is_empty() { - not_found() - } else { - if let Ok(( - id, - iota_id, - username, - _, - _, - _, - _, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - let cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(id)), - ) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - (StatusCode::OK, cv.to_json().to_string()) - } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) - } - } - } - } - "public_key" => (StatusCode::OK, public_key_to_base64(&get_public_key())), - "user" => { - if path_parts.len() != 4 { - bad_request() - } else { - let id = path_parts[3]; - let id: i64 = id.parse().unwrap_or(0); - if id == 0 { - bad_request() - } else { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - let mut cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(id)), - ) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - if let Some(display) = display { - cv = cv.add_data_str(DataTypes::display, display); - } - if let Some(status) = status { - cv = cv.add_data_str(DataTypes::status, status); - } - if let Some(about) = about { - cv = cv.add_data_str(DataTypes::about, about); - } - if let Some(avatar) = avatar { - cv = cv.add_data_str( - DataTypes::avatar, - base64::engine::general_purpose::STANDARD.encode(avatar), - ); - } - (StatusCode::OK, cv.to_json().to_string()) - } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) - } - } - } - } - _ => { - let id = path_parts[2]; - let id: i64 = id.parse().unwrap_or(0); - if id == 0 { - bad_request() - } else { - bad_request() - } - } - }, - _ => not_found(), + log!("Path parts: {:?}", path_parts); + let (status, body_text) = match path_parts.as_slice() { + ["api", "download", "iota_frontend"] => { + let file = load_file("downloads", "iota_frontend.zip"); + (StatusCode::OK, file) } - } else { - not_found() + ["api", "get", "omikron"] => { + if let Ok(omikron_conn) = get_random_omikron().await { + if let Ok((public_key, ip_address)) = + sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await + { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_conn.get_omikron_id().await, + public_key, + ip_address + ), + ) + } else { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "selected an invalid omikron".to_string(), + ) + } + } else { + ( + StatusCode::NOT_FOUND, + "couldn't find online omikron".to_string(), + ) + } + } + ["api", "get", "omikron", id] => { + let id = id.parse::().unwrap_or(0); + if id == 0 { + not_found() + } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + id, public_key, ip_address + ), + ) + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await + { + if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + ( + StatusCode::OK, + format!( + "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", + omikron_id, public_key, ip_address + ), + ) + } else { + not_found() + } + } else { + not_found() + } + } else { + not_found() + } + } + ["api", "get", "id", username] => { + if username.is_empty() { + not_found() + } else if let Ok(( + id, + iota_id, + username, + _, + _, + _, + _, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + let cv = CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); + (StatusCode::OK, cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } + ["api", "get", "public_key"] => (StatusCode::OK, public_key_to_base64(&get_public_key())), + ["api", "get", "user", id] => { + let id: i64 = id.parse().unwrap_or(0); + if id == 0 { + bad_request() + } else if let Ok(( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + let mut cv = CommunicationValue::new(CommunicationType::success) + .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::public_key, public_key) + .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) + .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) + .add_data( + DataTypes::sub_level, + JsonValue::Number(Number::from(sub_level)), + ) + .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); + if let Some(display) = display { + cv = cv.add_data_str(DataTypes::display, display); + } + if let Some(status) = status { + cv = cv.add_data_str(DataTypes::status, status); + } + if let Some(about) = about { + cv = cv.add_data_str(DataTypes::about, about); + } + if let Some(avatar) = avatar { + cv = cv.add_data_str( + DataTypes::avatar, + base64::engine::general_purpose::STANDARD.encode(avatar), + ); + } + (StatusCode::OK, cv.to_json().to_string()) + } else { + ( + StatusCode::OK, + CommunicationValue::new(CommunicationType::error_not_found) + .to_json() + .to_string(), + ) + } + } + _ => ( + StatusCode::INTERNAL_SERVER_ERROR, + CommunicationValue::new(CommunicationType::error) + .to_json() + .to_string(), + ), }; - let body = Full::new(Bytes::from(body_text.to_string())); - HttpResponse::builder().status(status).body(body).unwrap() + let body_bytes = body_text.to_string().into_bytes(); + let body = BoxBody::new(body_bytes.clone()); + HttpResponse::new(status).set_body(body) } + pub fn bad_request() -> (StatusCode, String) { (StatusCode::BAD_REQUEST, "400 Bad Request".to_string()) } diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 3eeb22b..b44d1b4 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,19 +1,16 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::server::omikron_manager; use crate::server::short_link::add_short_link; +use crate::server::socket::{WsSendMessage, WsSession}; use crate::sql::connection_status::UserStatus; use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_err, log_in, log_out}; -use axum::extract::ws::WebSocket; -use axum::extract::ws::{Message, Utf8Bytes}; +use crate::{get_private_key, get_public_key, log_in, log_out}; +use actix::Addr; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use futures::stream::SplitSink; -use futures::stream::SplitStream; -use futures_util::SinkExt; use json::JsonValue; use json::number::Number; use rand::Rng; @@ -24,8 +21,7 @@ use uuid::Uuid; use x448::PublicKey; pub struct OmikronConnection { - pub sender: Arc>>, - pub receiver: Arc>>, + pub ws_addr: Arc>>, pub omikron_id: Arc>, pub pub_key: Arc>>>, identified: Arc>, @@ -39,13 +35,9 @@ pub struct OmikronConnection { } impl OmikronConnection { - pub fn new( - sender: SplitSink, - receiver: SplitStream, - ) -> Arc { + pub fn new(ws_addr: Addr) -> Arc { Arc::new(Self { - sender: Arc::new(RwLock::new(sender)), - receiver: Arc::new(RwLock::new(receiver)), + ws_addr: Arc::new(RwLock::new(ws_addr)), omikron_id: Arc::new(RwLock::new(0)), pub_key: Arc::new(RwLock::new(None)), identified: Arc::new(RwLock::new(false)), @@ -56,25 +48,20 @@ impl OmikronConnection { }) } pub async fn send_message(&self, cv: &CommunicationValue) { - let mut sender = self.sender.write().await; - let message_text = Message::Text(Utf8Bytes::from(cv.to_json().to_string())); + let text = cv.to_json().to_string(); + if !cv.is_type(CommunicationType::pong) { log_out!( *self.omikron_id.read().await, PrintType::Omikron, "{}", - cv.to_json().to_string() - ); - } - if let Err(e) = sender.send(message_text).await { - log_err!( - *self.omikron_id.read().await, - PrintType::Omikron, - "WebSocket send error: {}", - e + text ); } + + self.ws_addr.read().await.do_send(WsSendMessage(text)); } + pub async fn get_omikron_id(&self) -> i64 { *self.omikron_id.read().await } @@ -948,7 +935,6 @@ impl OmikronConnection { self.send_message(&error).await; } pub async fn close(&self) { - let mut sender = self.sender.write().await; if self.is_identified().await { let omikron_id = self.get_omikron_id().await; if omikron_id != 0 { @@ -957,7 +943,6 @@ impl OmikronConnection { user_online_tracker::untrack_omikron(omikron_id).await; } } - let _ = sender.close().await; } pub async fn handle_close(self: Arc) { if self.is_identified().await { diff --git a/src/server/server.rs b/src/server/server.rs index afa0244..5167444 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,389 +1,83 @@ -<<<<<<< HEAD -use base64::Engine; -use base64::engine::general_purpose::STANDARD; -use bytes::Bytes; -use futures_util::TryFutureExt; -use http_body_util::BodyExt; -use http_body_util::Full; -use hyper::server::conn::{http1, http2}; -use hyper::{Method, StatusCode}; -use hyper::{Request as HttpRequest, Response as HttpResponse, body::Incoming, upgrade}; -use hyper_util::rt::TokioExecutor; -use hyper_util::rt::tokio::TokioIo; -use hyper_util::service::TowerToHyperService; -use sha1::{Digest, Sha1}; -use std::io; -use std::net::SocketAddr; -use std::result::Result::Ok; -use std::{future::Future, pin::Pin, time::Duration}; -use tokio::net::TcpListener; -use tower::Service; -======= -use axum::{ - Router, - body::Body, - extract::{ConnectInfo, OriginalUri, Path, ws::WebSocketUpgrade}, - response::{IntoResponse, Redirect}, - routing::get, +use crate::{ + log, + server::{api, short_link::get_short_link, socket}, + util::file_util::get_directory, }; -use pnet::datalink::NetworkInterface; -use std::net::SocketAddr; -use std::time::Duration; -use tokio::net::TcpListener; ->>>>>>> 7f78c8669b36cbe39755d69cccd6971e56e10290 +use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; +use actix_web_actors::ws; -use crate::log; -use crate::server::api; -use crate::server::short_link::get_short_link; -use crate::server::socket; +use rustls::ServerConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls_pemfile::{certs, pkcs8_private_keys}; -<<<<<<< HEAD -// --- ApiService for HTTP/2 --- +use std::fs::File; +use std::io::BufReader; -#[derive(Clone)] -struct ApiService { - _peer_addr: SocketAddr, -} +pub async fn start(port: u16) -> anyhow::Result<()> { + let mut cert_reader = + BufReader::new(File::open(format!("{}/certs/cert.pem", get_directory()))?); -impl Service> for ApiService { - type Response = HttpResponse>; - type Error = io::Error; - type Future = Pin> + Send>>; -======= -pub async fn start(port: u16) -> bool { - let app = Router::new() - .route("/ws/omikron", get(ws_handler)) - .route("/direct/{short}", get(direct_handler)) - .fallback(fallback_handler); - run_http_server(port, app).await -} + let mut key_reader = BufReader::new(File::open(format!("{}/certs/key.pem", get_directory()))?); -async fn ws_handler( - ws: WebSocketUpgrade, - OriginalUri(uri): OriginalUri, - ConnectInfo(_): ConnectInfo, -) -> impl IntoResponse { - log!("Attempting WebSocket upgrade on {}", uri.path()); - let path = uri.path().to_string(); ->>>>>>> 7f78c8669b36cbe39755d69cccd6971e56e10290 + let cert_chain: Vec> = + certs(&mut cert_reader).collect::>()?; - ws.on_upgrade(async move |socket| socket::handle(path, socket)) -} + let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) + .map(|res| res.map(Into::into)) + .collect::>()?; -<<<<<<< HEAD - fn call(&mut self, req: HttpRequest) -> Self::Future { - let (parts, body) = req.into_parts(); - let path = parts.uri.path().to_string(); - let headers = parts.headers.clone(); + let key = keys.remove(0); - let fut = async move { - if path.starts_with("/api") { - let whole_body = - tokio::time::timeout(Duration::from_secs(10), body.collect()).await??; - let bytes = whole_body.to_bytes(); - let body_string: Option = String::from_utf8(bytes.to_vec()).ok(); - Ok(api::handle(&path, headers, body_string).await) - } else if path.starts_with("/direct") { - let short = path.replace("/direct/", ""); - if let Ok(long) = get_short_link(&short).await { - let response = HttpResponse::builder() - .status(StatusCode::FOUND) - .header("Location", long) - .body(Full::new(Bytes::from(""))) - .unwrap(); - Ok(response) - } else { - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from("Short link not found"))) - .unwrap(); - Ok(response) - } - } else { - // Not a WebSocket, /api, or /direct, so it's a 404 - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from("Not Found"))) - .unwrap(); - Ok(response) - } - }; + let mut config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key)?; - Box::pin(fut.map_err(|err: color_eyre::eyre::ErrReport| { - io::Error::new( - io::ErrorKind::Other, - format!("Error in API request: {}", err), - ) - })) - } -} + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; -// --- WebSocketService for HTTP/1.1 --- + let addr = format!("0.0.0.0:{port}"); + log!(" Server on {}", addr); -#[derive(Clone)] -struct WebSocketService { - _peer_addr: SocketAddr, -} - -impl Service> for WebSocketService { - type Response = HttpResponse>; - type Error = io::Error; - type Future = Pin> + Send>>; - - fn poll_ready( - &mut self, - _cx: &mut std::task::Context<'_>, - ) -> std::task::Poll> { - std::task::Poll::Ready(std::io::Result::Ok(())) - } - - fn call(&mut self, req: HttpRequest) -> Self::Future { - let (parts, body) = req.into_parts(); - let method = parts.method.clone(); - let path = parts.uri.path().to_string(); - let headers = parts.headers.clone(); - - let fut = async move { - let is_websocket_upgrade = path.starts_with("/ws") - && method == Method::GET - && headers - .get("connection") - .map(|v| v.to_str().unwrap_or("").contains("Upgrade")) - .unwrap_or(false) - && headers.get("upgrade").map(|v| v.to_str().unwrap_or("")) == Some("websocket"); - - if is_websocket_upgrade { - log!("Attempting WebSocket upgrade on /ws"); - if let Some(sec_websocket_key) = headers.get("sec-websocket-key") { - let sec_websocket_key_str = - sec_websocket_key.to_str().unwrap_or("").to_string(); - let sec_websocket_accept = calculate_accept_key(&sec_websocket_key_str); - - let response = HttpResponse::builder() - .status(StatusCode::SWITCHING_PROTOCOLS) - .header("Upgrade", "websocket") - .header("Connection", "Upgrade") - .header("Sec-WebSocket-Accept", sec_websocket_accept) - .body(Full::new(Bytes::from(""))) - .unwrap(); - - let req_for_upgrade = HttpRequest::from_parts(parts, body); - let upgrades = upgrade::on(req_for_upgrade); - socket::handle(path, upgrades); - Ok(response) - } else { - log!("No Sec-WebSocket-Key found in request headers"); - let response = HttpResponse::builder() - .status(StatusCode::BAD_REQUEST) - .body(Full::new(Bytes::from("Missing Sec-WebSocket-Key"))) - .unwrap(); - Ok(response) - } - } else { - let response = HttpResponse::builder() - .status(StatusCode::NOT_FOUND) - .body(Full::new(Bytes::from( - "Not Found: Use the API server for non-WebSocket requests.", - ))) - .unwrap(); - Ok(response) - } - }; - - Box::pin(fut.map_err(|_err: color_eyre::eyre::ErrReport| { - io::Error::new(io::ErrorKind::Other, "Error in WebSocket request") - })) - } -} - -async fn run_api_server(port: u16) -> bool { - let mut ip = "0.0.0.0".to_string(); - for iface in pnet::datalink::interfaces() { - if let Some(ip_net) = iface.ips.iter().find(|ip_net| { - ip_net.is_ipv4() - && (ip_net.ip().to_string().starts_with("10.") - || ip_net.ip().to_string().starts_with("192.")) - }) { - ip = ip_net.ip().to_string(); - break; - } - } - - let addr = SocketAddr::new(ip.parse().unwrap(), port); - let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener_res { - log!( - "[FATAL] Failed to bind API server to port {}: {:?}", - port, - e - ); - return false; - } - let listener = listener_res.unwrap(); - log!("API Server (HTTP/2) listening on {}:{}", ip, port); - - tokio::spawn(async move { - loop { - if let Ok((stream, addr)) = listener.accept().await { - let service = ApiService { _peer_addr: addr }; - let io = TokioIo::new(stream); - tokio::spawn(async move { - let conn = http2::Builder::new(TokioExecutor::new()) - .serve_connection(io, TowerToHyperService::new(service)); - if let Err(err) = conn.await { - if !err - .to_string() - .starts_with("error shutting down connection") - { - log!("API server connection error: {:?}", err); - } - } - }); - } - } - }); - - true -} - -async fn run_websocket_server(port: u16) -> bool { - let mut ip = "0.0.0.0".to_string(); - for iface in pnet::datalink::interfaces() { - if let Some(ip_net) = iface.ips.iter().find(|ip_net| { - ip_net.is_ipv4() - && (ip_net.ip().to_string().starts_with("10.") - || ip_net.ip().to_string().starts_with("192.")) - }) { - ip = ip_net.ip().to_string(); - break; - } - } - - let addr = SocketAddr::new(ip.parse().unwrap(), port); - let listener_res = TcpListener::bind(format!("0.0.0.0:{}", port)).await; - if let Err(e) = listener_res { - log!( - "[FATAL] Failed to bind WebSocket server to port {}: {:?}", - port, - e - ); - return false; - } - let listener = listener_res.unwrap(); - log!("WebSocket Server (HTTP/1.1) listening on {}:{}", ip, port); - - tokio::spawn(async move { - loop { - if let Ok((stream, addr)) = listener.accept().await { - let service = WebSocketService { _peer_addr: addr }; - let io = TokioIo::new(stream); - tokio::spawn(async move { - let conn = http1::Builder::new() - .preserve_header_case(true) - .title_case_headers(true) - .serve_connection(io, TowerToHyperService::new(service)) - .with_upgrades(); - if let Err(err) = conn.await { - if !err - .to_string() - .starts_with("error shutting down connection") - { - log!("WebSocket server connection error: {:?}", err); - } - } - }); - } - } - }); - - true -} - -// --- Main Start function --- - -pub async fn start(api_port: u16, ws_port: u16) { - // We are not using TLS for this setup as per the request's focus on splitting protocols. - // The previous TLS loading logic is removed for simplicity. - tokio::spawn(run_api_server(api_port)); - tokio::spawn(run_websocket_server(ws_port)); -} - -fn calculate_accept_key(key: &str) -> String { - let websocket_guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - let mut sha1 = Sha1::new(); - sha1.update(key.as_bytes()); - sha1.update(websocket_guid.as_bytes()); - let result = sha1.finalize(); - STANDARD.encode(result) -======= -async fn direct_handler(Path(short): Path) -> impl IntoResponse { - match get_short_link(&short).await { - Ok(long) => Redirect::temporary(&long), - Err(_) => Redirect::temporary("https://tensamin.net"), - } -} - -async fn fallback_handler( - OriginalUri(uri): OriginalUri, - headers: axum::http::HeaderMap, - body: Body, -) -> impl IntoResponse { - let path = uri.path().to_string(); - - let whole_body = tokio::time::timeout( - Duration::from_secs(10), - axum::body::to_bytes(body, 1024 * 1024 * 10), - ) - .await; - - let body_string = match whole_body { - Ok(Ok(bytes)) => String::from_utf8(bytes.to_vec()).ok(), - _ => None, - }; - - api::handle(&path, headers, body_string).await -} - -async fn run_http_server(port: u16, app: Router) -> bool { - let ip = find_local_ip(); - let listener = match TcpListener::bind(format!("0.0.0.0:{}", port)).await { - Ok(l) => l, - Err(e) => { - log!("Failed to bind to port {}: {:?}", port, e); - return false; - } - }; - - log!( - "Standard Server listening for HTTP and WS on {}:{}", - ip, - port - ); - - axum::serve( - listener, - app.into_make_service_with_connect_info::(), - ) - .await - .map(|_| true) - .unwrap_or_else(|e| { - log!("Server error: {}", e); - false + HttpServer::new(move || { + App::new() + .route("/api/{path:.*}", web::to(api_handler)) + .route("/direct/{path:.*}", web::to(direct_handler)) + .route("/ws/{path:.*}", web::get().to(ws_handler)) }) + .bind_rustls_0_23(addr, config)? + .run() + .await?; + + Ok(()) +} +async fn direct_handler(req: HttpRequest) -> impl Responder { + let path = req.uri().path().to_string(); + let short = path.replace("/direct/", ""); + + if let Ok(long) = get_short_link(&short).await { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, long)) + .finish() + } else { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, "https://tensamin.net")) + .finish() + } +} +async fn ws_handler( + req: HttpRequest, + stream: web::Payload, + path: web::Path, +) -> Result { + let path = path.into_inner(); + println!("WS handler reached: {}", path); + + ws::start(socket::WsSession::new(path), &req, stream) } -fn find_local_ip() -> String { - for iface in pnet::datalink::interfaces() { - let iface: NetworkInterface = iface; - if !iface.ips.is_empty() { - let ipsv = format!("{}", iface.ips[0]); - let ips: &str = ipsv.split('/').next().unwrap(); - if ips.starts_with("10.") || ips.starts_with("192.") { - return ips.to_string(); - } - } - } - "0.0.0.0".to_string() ->>>>>>> 7f78c8669b36cbe39755d69cccd6971e56e10290 +async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { + let path = req.uri().path().to_string(); + let body_string = String::from_utf8_lossy(&body).to_string(); + + api::handle(&path, Some(body_string)).await } diff --git a/src/server/socket.rs b/src/server/socket.rs old mode 100644 new mode 100755 index 25a00f7..7d04b26 --- a/src/server/socket.rs +++ b/src/server/socket.rs @@ -1,99 +1,129 @@ use std::sync::Arc; +use std::time::{Duration, Instant}; -use axum::extract::ws::{Message, Utf8Bytes, WebSocket}; -use futures::StreamExt; +use actix::{Actor, ActorContext, AsyncContext, StreamHandler}; +use actix_web_actors::ws; use crate::data::communication::{CommunicationType, CommunicationValue}; use crate::log; use crate::server::omikron_connection::OmikronConnection; -pub fn handle(path: String, upgrades: WebSocket) { - tokio::spawn(async move { - log!( - "[ws] Spawning new task to handle WebSocket upgrade for path: {}", - path - ); - log!("[ws] WebSocket upgrade successful for path: {}", path); +const IDLE_TIMEOUT: Duration = Duration::from_secs(30); - log!( - "[ws] WebSocket handshake successful, handling connection for {}", - path - ); +use actix::Message; - let (writer, reader) = upgrades.split(); - if path == "/ws/omikron" { - let connection = OmikronConnection::new(writer, reader); - tokio::spawn(start_connecteable_handler(connection)); - } +#[derive(Message)] +#[rtype(result = "()")] +pub struct WsSendMessage(pub String); - log!( - "[ws] WebSocket handling task for path: {} is finished.", - path - ); - }); +impl actix::Handler for WsSession { + type Result = (); + + fn handle(&mut self, msg: WsSendMessage, ctx: &mut Self::Context) { + ctx.text(msg.0); + } } -pub async fn start_connecteable_handler(connection: Arc) { - use futures::SinkExt; - use tokio::time::Duration; - const IDLE_TIMEOUT: Duration = Duration::from_secs(30); +pub struct WsSession { + path: String, + last_heartbeat: Instant, + omikron: Option>, +} - log!("[ws_handler] Starting connection handler loop."); - loop { - let mut receiver_guard = connection.receiver.write().await; - - match tokio::time::timeout(IDLE_TIMEOUT, receiver_guard.next()).await { - Ok(Some(Ok(msg))) => { - drop(receiver_guard); - - match msg { - Message::Text(text) => { - let conn_clone = connection.clone(); - tokio::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - Message::Close(_) => { - log!("[ws_handler] Received 'Close' message. Breaking loop."); - break; - } - Message::Pong(_) => { - log!("[ws_handler] Received 'Pong'. Connection is alive."); - } - _ => { - log!("[ws_handler] Received unhandled message type."); - } +impl WsSession { + pub fn new(path: String) -> Self { + Self { + path, + last_heartbeat: Instant::now(), + omikron: None, + } + } + + fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext) { + ctx.run_interval(Duration::from_secs(5), |act, ctx| { + if Instant::now().duration_since(act.last_heartbeat) > IDLE_TIMEOUT { + log!("[ws_handler] Heartbeat failed. Disconnecting."); + ctx.close(None); + ctx.stop(); + return; + } + + let ping = CommunicationValue::new(CommunicationType::ping) + .to_json() + .to_string(); + + ctx.text(ping); + }); + } +} + +impl Actor for WsSession { + type Context = ws::WebsocketContext; + + fn started(&mut self, ctx: &mut Self::Context) { + self.start_heartbeat(ctx); + + if self.path == "omikron" { + let addr = ctx.address(); + let connection = OmikronConnection::new(addr); + self.omikron = Some(connection); + } + } + + fn stopped(&mut self, _: &mut Self::Context) { + log!( + "[ws] WebSocket handling task for path: {} is finished.", + self.path + ); + + if let Some(conn) = &self.omikron { + let conn = conn.clone(); + actix_rt::spawn(async move { + conn.handle_close().await; + }); + } + } +} +impl StreamHandler> for WsSession { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { + match msg { + Ok(ws::Message::Text(text)) => { + self.last_heartbeat = Instant::now(); + + if let Some(conn) = &self.omikron { + let conn_clone = conn.clone(); + actix_rt::spawn(async move { + conn_clone.handle_message(text.to_string()).await; + }); } } - Ok(Some(Err(e))) => { - log!("[ERROR] WS Error: {}. Breaking loop.", e); - break; + + Ok(ws::Message::Ping(msg)) => { + self.last_heartbeat = Instant::now(); + ctx.pong(&msg); } - Ok(_) => { - log!("[ws_handler] WebSocket stream closed by peer. Breaking loop."); - break; + + Ok(ws::Message::Pong(_)) => { + self.last_heartbeat = Instant::now(); + log!("[ws_handler] Received Pong. Connection alive."); } - Err(_) => { - drop(receiver_guard); - log!("[ws_handler] Timeout: Dropped receiver lock. Sending a ping."); - let mut sender = connection.sender.write().await; - log!("[ws_handler] Acquired sender lock for ping."); - if let Err(e) = sender - .send(Message::Text(Utf8Bytes::from( - CommunicationValue::new(CommunicationType::ping) - .to_json() - .to_string(), - ))) - .await - { - log!("[ERROR] Failed to send ping: {}. Closing connection.", e); - break; - } - log!("[ws_handler] Ping sent successfully."); + + Ok(ws::Message::Close(reason)) => { + log!("[ws_handler] Received Close. Disconnecting."); + ctx.close(reason); + ctx.stop(); + } + + Ok(ws::Message::Binary(_)) => { + log!("[ws_handler] Binary message ignored."); + } + + Err(e) => { + log!("[ERROR] WS Error: {}. Closing.", e); + ctx.stop(); } + + _ => {} } } - log!("[ws_handler] Connection handler loop finished."); - connection.handle_close().await; - log!("[ws_handler] Connection closed."); } From 3340eca421d7ac6d694e06e5747cd95e952ca725 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:35:21 +0100 Subject: [PATCH 061/220] [Fix] Filedownloads --- src/server/api.rs | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/server/api.rs b/src/server/api.rs index bc240dc..f826e3e 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,21 +1,27 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; -use crate::util::file_util::load_file; -use crate::{get_public_key, log}; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, }; use actix_web::HttpResponse; -use actix_web::body::BoxBody; -use actix_web::http::StatusCode; +use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; use json::number::Number; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { + if path == "OPTIONS" { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) + .insert_header(("Access-Control-Allow-Headers", "*")) + .finish(); + } + let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); let _body: Option = if body_string.is_some() { @@ -27,11 +33,28 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } else { None }; - log!("Path parts: {:?}", path_parts); + let (status, body_text) = match path_parts.as_slice() { ["api", "download", "iota_frontend"] => { - let file = load_file("downloads", "iota_frontend.zip"); - (StatusCode::OK, file) + let file_path = "downloads/[iota_frontend].zip"; + + match std::fs::read(file_path) { + Ok(file_bytes) => { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Content-Type", "application/zip")) + .insert_header(( + "Content-Disposition", + "attachment; filename=\"iota_frontend.zip\"", + )) + .body(file_bytes); + } + Err(_) => { + return HttpResponse::NotFound() + .insert_header(("Access-Control-Allow-Origin", "*")) + .body("File not found"); + } + } } ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { @@ -205,9 +228,13 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { .to_string(), ), }; - let body_bytes = body_text.to_string().into_bytes(); - let body = BoxBody::new(body_bytes.clone()); - HttpResponse::new(status).set_body(body) + let body_bytes = body_text.into_bytes(); + + HttpResponse::build(status) + .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) + .body(body_bytes) } pub fn bad_request() -> (StatusCode, String) { From bbe18ee759a75161974c5cc03694639183d5aee8 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 15 Feb 2026 01:35:21 +0100 Subject: [PATCH 062/220] [Fix] Filedownloads --- src/server/api.rs | 47 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/server/api.rs b/src/server/api.rs index bc240dc..f826e3e 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,21 +1,27 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; -use crate::util::file_util::load_file; -use crate::{get_public_key, log}; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, }; use actix_web::HttpResponse; -use actix_web::body::BoxBody; -use actix_web::http::StatusCode; +use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; use json::number::Number; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { + if path == "OPTIONS" { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) + .insert_header(("Access-Control-Allow-Headers", "*")) + .finish(); + } + let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); let _body: Option = if body_string.is_some() { @@ -27,11 +33,28 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } else { None }; - log!("Path parts: {:?}", path_parts); + let (status, body_text) = match path_parts.as_slice() { ["api", "download", "iota_frontend"] => { - let file = load_file("downloads", "iota_frontend.zip"); - (StatusCode::OK, file) + let file_path = "downloads/[iota_frontend].zip"; + + match std::fs::read(file_path) { + Ok(file_bytes) => { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Content-Type", "application/zip")) + .insert_header(( + "Content-Disposition", + "attachment; filename=\"iota_frontend.zip\"", + )) + .body(file_bytes); + } + Err(_) => { + return HttpResponse::NotFound() + .insert_header(("Access-Control-Allow-Origin", "*")) + .body("File not found"); + } + } } ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { @@ -205,9 +228,13 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { .to_string(), ), }; - let body_bytes = body_text.to_string().into_bytes(); - let body = BoxBody::new(body_bytes.clone()); - HttpResponse::new(status).set_body(body) + let body_bytes = body_text.into_bytes(); + + HttpResponse::build(status) + .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) + .body(body_bytes) } pub fn bad_request() -> (StatusCode, String) { From 07988501d3675b509980810ffbc789307677be6c Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:45:16 +0100 Subject: [PATCH 063/220] [Fix] logging & not sending empty data --- src/server/omikron_connection.rs | 35 ++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index b44d1b4..d6eb54a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -213,12 +213,14 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { + log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { user_online_tracker::track_user_status(user_id, UserStatus::online, omikron_id); } return; } if cv.is_type(CommunicationType::user_disconnected) { + log_in!(PrintType::Omega, "User disconnected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( @@ -260,6 +262,7 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::iota_disconnected) { + log_in!(PrintType::Omega, "IOTA disconnected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); @@ -320,7 +323,7 @@ impl OmikronConnection { let mut response = CommunicationValue::new(CommunicationType::get_user_data) .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::username, username.clone()) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) .add_data( @@ -337,13 +340,21 @@ impl OmikronConnection { ); if let Some(display) = display { - response = response.add_data_str(DataTypes::display, display); + if display.is_empty() { + response = response.add_data_str(DataTypes::display, username); + } else { + response = response.add_data_str(DataTypes::display, display); + } } if let Some(status) = status { - response = response.add_data_str(DataTypes::status, status); + if !status.is_empty() { + response = response.add_data_str(DataTypes::status, status); + } } if let Some(about) = about { - response = response.add_data_str(DataTypes::about, about); + if !about.is_empty() { + response = response.add_data_str(DataTypes::about, about); + } } if let Some(avatar) = avatar { response = @@ -405,7 +416,7 @@ impl OmikronConnection { let mut response = CommunicationValue::new(CommunicationType::get_user_data) .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::username, username.clone()) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) .add_data( @@ -422,13 +433,21 @@ impl OmikronConnection { ); if let Some(display) = display { - response = response.add_data_str(DataTypes::display, display); + if display.is_empty() { + response = response.add_data_str(DataTypes::display, username); + } else { + response = response.add_data_str(DataTypes::display, display); + } } if let Some(status) = status { - response = response.add_data_str(DataTypes::status, status); + if !status.is_empty() { + response = response.add_data_str(DataTypes::status, status); + } } if let Some(about) = about { - response = response.add_data_str(DataTypes::about, about); + if !about.is_empty() { + response = response.add_data_str(DataTypes::about, about); + } } if let Some(avatar) = avatar { response = From 0af5254c405bf59552e14137ae4725ac2aaf3f8c Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 15 Feb 2026 12:45:16 +0100 Subject: [PATCH 064/220] [Fix] logging & not sending empty data --- src/server/omikron_connection.rs | 35 ++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index b44d1b4..d6eb54a 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -213,12 +213,14 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { + log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { user_online_tracker::track_user_status(user_id, UserStatus::online, omikron_id); } return; } if cv.is_type(CommunicationType::user_disconnected) { + log_in!(PrintType::Omega, "User disconnected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { if let Some(status) = user_online_tracker::get_user_status(user_id) { user_online_tracker::track_user_status( @@ -260,6 +262,7 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::iota_disconnected) { + log_in!(PrintType::Omega, "IOTA disconnected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); @@ -320,7 +323,7 @@ impl OmikronConnection { let mut response = CommunicationValue::new(CommunicationType::get_user_data) .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::username, username.clone()) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) .add_data( @@ -337,13 +340,21 @@ impl OmikronConnection { ); if let Some(display) = display { - response = response.add_data_str(DataTypes::display, display); + if display.is_empty() { + response = response.add_data_str(DataTypes::display, username); + } else { + response = response.add_data_str(DataTypes::display, display); + } } if let Some(status) = status { - response = response.add_data_str(DataTypes::status, status); + if !status.is_empty() { + response = response.add_data_str(DataTypes::status, status); + } } if let Some(about) = about { - response = response.add_data_str(DataTypes::about, about); + if !about.is_empty() { + response = response.add_data_str(DataTypes::about, about); + } } if let Some(avatar) = avatar { response = @@ -405,7 +416,7 @@ impl OmikronConnection { let mut response = CommunicationValue::new(CommunicationType::get_user_data) .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username) + .add_data_str(DataTypes::username, username.clone()) .add_data_str(DataTypes::public_key, public_key) .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) .add_data( @@ -422,13 +433,21 @@ impl OmikronConnection { ); if let Some(display) = display { - response = response.add_data_str(DataTypes::display, display); + if display.is_empty() { + response = response.add_data_str(DataTypes::display, username); + } else { + response = response.add_data_str(DataTypes::display, display); + } } if let Some(status) = status { - response = response.add_data_str(DataTypes::status, status); + if !status.is_empty() { + response = response.add_data_str(DataTypes::status, status); + } } if let Some(about) = about { - response = response.add_data_str(DataTypes::about, about); + if !about.is_empty() { + response = response.add_data_str(DataTypes::about, about); + } } if let Some(avatar) = avatar { response = From 1f636a04d7020dab43b3b696f3cba9c19353d486 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:52:44 +0100 Subject: [PATCH 065/220] [Change] user state names --- Cargo.toml | 2 +- src/server/omikron_connection.rs | 8 ++++++-- src/sql/connection_status.rs | 11 +++++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f89a8a4..01ff457 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = { version = "0.8.8", features = [ "ws" ] } +axum = { version = "0.8.8", features = [ "ws", "http2" ] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 3eeb22b..2dfc483 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -227,7 +227,11 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, UserStatus::online, omikron_id); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_online, + omikron_id, + ); } return; } @@ -293,7 +297,7 @@ impl OmikronConnection { if let Some(user_id) = user_id_json.as_i64() { user_online_tracker::track_user_status( user_id, - UserStatus::online, + UserStatus::user_online, omikron_id, ); } diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index d5b8603..a175e6d 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -4,12 +4,15 @@ use strum_macros::EnumIter; #[derive(Debug, Clone, PartialEq, EnumIter, Eq)] #[allow(unused, non_camel_case_types)] pub enum UserStatus { - online, - phone_online, - away, - do_not_disturb, user_offline, + user_online, + user_dnd, + user_idle, + user_wc, + user_borked, iota_offline, + iota_online, + iota_borked, } #[allow(unused)] impl UserStatus { From edc43ea2a5ff27abea9c931cf48def80feea39de Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:52:44 +0100 Subject: [PATCH 066/220] [Change] user state names --- Cargo.toml | 2 +- src/server/omikron_connection.rs | 8 ++++++-- src/sql/connection_status.rs | 11 +++++++---- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f89a8a4..01ff457 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = { version = "0.8.8", features = [ "ws" ] } +axum = { version = "0.8.8", features = [ "ws", "http2" ] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 3eeb22b..2dfc483 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -227,7 +227,11 @@ impl OmikronConnection { // ONLINE STATUS TRACKING if cv.is_type(CommunicationType::user_connected) { if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status(user_id, UserStatus::online, omikron_id); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_online, + omikron_id, + ); } return; } @@ -293,7 +297,7 @@ impl OmikronConnection { if let Some(user_id) = user_id_json.as_i64() { user_online_tracker::track_user_status( user_id, - UserStatus::online, + UserStatus::user_online, omikron_id, ); } diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index d5b8603..a175e6d 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -4,12 +4,15 @@ use strum_macros::EnumIter; #[derive(Debug, Clone, PartialEq, EnumIter, Eq)] #[allow(unused, non_camel_case_types)] pub enum UserStatus { - online, - phone_online, - away, - do_not_disturb, user_offline, + user_online, + user_dnd, + user_idle, + user_wc, + user_borked, iota_offline, + iota_online, + iota_borked, } #[allow(unused)] impl UserStatus { From defb4c0426bc8a29b10126b8d3fb645c6c9144c8 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:33:36 +0100 Subject: [PATCH 067/220] [Fix] Omikron Con no longer breaks on iota disconnect :D --- src/server/omikron_connection.rs | 25 ++++++++---- src/sql/sql.rs | 4 +- src/sql/user_online_tracker.rs | 69 +++++++++++++++++++++----------- 3 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f1634fc..11dc310 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -63,7 +63,11 @@ impl OmikronConnection { } pub async fn get_omikron_id(&self) -> i64 { - *self.omikron_id.read().await + let omikron_id = { + let guard = self.omikron_id.read().await; + guard.clone() + }; + omikron_id } pub async fn is_identified(&self) -> bool { *self.identified.read().await && *self.challenged.read().await @@ -267,18 +271,23 @@ impl OmikronConnection { } if cv.is_type(CommunicationType::iota_disconnected) { log_in!(PrintType::Omega, "IOTA disconnected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - let iota_offline = - user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); + + if let Some(v) = cv.get_data(DataTypes::iota_id) { + if let Some(iota_id) = v.as_i64() { + let iota_offline = + user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); + } } } } + return; } + if cv.is_type(CommunicationType::sync_client_iota_status) { if let Some(json::JsonValue::Array(user_ids)) = cv.get_data(DataTypes::user_ids).cloned() diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 1994551..8445e26 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -65,9 +65,9 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { about VARCHAR(200) COLLATE utf8mb4_bin, avatar MEDIUMBLOB, sub_level INT(11) NOT NULL DEFAULT 0, - sub_end BIGINT(20) NOT NULL, + sub_end BIGINT(20) NOT NULL DEFAULT 0, public_key TEXT NOT NULL COLLATE utf8mb4_bin, - private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin, + private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', iota_id BIGINT UNSIGNED NOT NULL, token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin )", diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 8cde17b..055c730 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -33,23 +33,30 @@ pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { } pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - if let Some(mut connections) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { - connections.retain(|&id| id != omikron_id); + let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { + let mut vec = r.value().clone(); + vec.retain(|&id| id != omikron_id); + let empty = vec.is_empty(); + drop(r); + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); + empty + } else { + false + }; - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) - { + if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { + let primary_id = *primary_ref.value(); + drop(primary_ref); + if primary_id == omikron_id { IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); } - - if connections.is_empty() { - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); - return true; - } } - false + + if connections_empty { + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + } + + connections_empty } pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { @@ -81,34 +88,48 @@ pub fn untrack_many_users(user_ids: &[i64]) { } pub async fn untrack_omikron(omikron_id: i64) { + // Step 1: Collect all iota_ids where this omikron_id is connected let mut offline_iotas = Vec::new(); + let mut primary_to_remove = Vec::new(); - IOTA_OMIKRON_CONNECTIONS.retain(|iota_id, connections| { - connections.retain(|id| *id != omikron_id); + // Collect iotas and primary info first + for r in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *r.key(); + let mut connections = r.value().clone(); + connections.retain(|&id| id != omikron_id); + + if connections.is_empty() { + offline_iotas.push(iota_id); + } if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(iota_id) + .get(&iota_id) .map(|p| *p == omikron_id) .unwrap_or(false) { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); + primary_to_remove.push(iota_id); } - if connections.is_empty() { - offline_iotas.push(*iota_id); - false - } else { - true - } - }); + // Update the connections vector after filtering + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); + } + // Step 2: Remove primary connections safely + for iota_id in primary_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + + // Step 3: Remove users that were on this omikron USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); + // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP for iota_id in offline_iotas { if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { for user in users { USER_STATUS_MAP.remove(&user.0); } } + // Finally remove the empty connections vector + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); } } From 2fde382555e0b74c0a81a6bbe7716952b6e9fbb5 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 19 Feb 2026 19:33:36 +0100 Subject: [PATCH 068/220] [Fix] Omikron Con no longer breaks on iota disconnect :D --- src/server/omikron_connection.rs | 25 ++++++++---- src/sql/sql.rs | 4 +- src/sql/user_online_tracker.rs | 69 +++++++++++++++++++++----------- 3 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f1634fc..11dc310 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -63,7 +63,11 @@ impl OmikronConnection { } pub async fn get_omikron_id(&self) -> i64 { - *self.omikron_id.read().await + let omikron_id = { + let guard = self.omikron_id.read().await; + guard.clone() + }; + omikron_id } pub async fn is_identified(&self) -> bool { *self.identified.read().await && *self.challenged.read().await @@ -267,18 +271,23 @@ impl OmikronConnection { } if cv.is_type(CommunicationType::iota_disconnected) { log_in!(PrintType::Omega, "IOTA disconnected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - let iota_offline = - user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); + + if let Some(v) = cv.get_data(DataTypes::iota_id) { + if let Some(iota_id) = v.as_i64() { + let iota_offline = + user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); + } } } } + return; } + if cv.is_type(CommunicationType::sync_client_iota_status) { if let Some(json::JsonValue::Array(user_ids)) = cv.get_data(DataTypes::user_ids).cloned() diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 1994551..8445e26 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -65,9 +65,9 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { about VARCHAR(200) COLLATE utf8mb4_bin, avatar MEDIUMBLOB, sub_level INT(11) NOT NULL DEFAULT 0, - sub_end BIGINT(20) NOT NULL, + sub_end BIGINT(20) NOT NULL DEFAULT 0, public_key TEXT NOT NULL COLLATE utf8mb4_bin, - private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin, + private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', iota_id BIGINT UNSIGNED NOT NULL, token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin )", diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 8cde17b..055c730 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -33,23 +33,30 @@ pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { } pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - if let Some(mut connections) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { - connections.retain(|&id| id != omikron_id); + let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { + let mut vec = r.value().clone(); + vec.retain(|&id| id != omikron_id); + let empty = vec.is_empty(); + drop(r); + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); + empty + } else { + false + }; - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) - { + if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { + let primary_id = *primary_ref.value(); + drop(primary_ref); + if primary_id == omikron_id { IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); } - - if connections.is_empty() { - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); - return true; - } } - false + + if connections_empty { + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + } + + connections_empty } pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { @@ -81,34 +88,48 @@ pub fn untrack_many_users(user_ids: &[i64]) { } pub async fn untrack_omikron(omikron_id: i64) { + // Step 1: Collect all iota_ids where this omikron_id is connected let mut offline_iotas = Vec::new(); + let mut primary_to_remove = Vec::new(); - IOTA_OMIKRON_CONNECTIONS.retain(|iota_id, connections| { - connections.retain(|id| *id != omikron_id); + // Collect iotas and primary info first + for r in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *r.key(); + let mut connections = r.value().clone(); + connections.retain(|&id| id != omikron_id); + + if connections.is_empty() { + offline_iotas.push(iota_id); + } if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(iota_id) + .get(&iota_id) .map(|p| *p == omikron_id) .unwrap_or(false) { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); + primary_to_remove.push(iota_id); } - if connections.is_empty() { - offline_iotas.push(*iota_id); - false - } else { - true - } - }); + // Update the connections vector after filtering + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); + } + // Step 2: Remove primary connections safely + for iota_id in primary_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + + // Step 3: Remove users that were on this omikron USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); + // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP for iota_id in offline_iotas { if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { for user in users { USER_STATUS_MAP.remove(&user.0); } } + // Finally remove the empty connections vector + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); } } From 9dc50fdfe0ae463991d023eef41150507a2135f9 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:19:13 +0100 Subject: [PATCH 069/220] [Fix] Use sender ID for change_user_data --- src/server/omikron_connection.rs | 126 +++++++++++++++---------------- 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 11dc310..e2e9ca7 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -92,6 +92,9 @@ impl OmikronConnection { return; } + // ────────────────────────────── + // Identification + // ────────────────────────────── if !self.is_identified().await { let identified = *self.identified.read().await; let challenged = *self.challenged.read().await; @@ -165,9 +168,6 @@ impl OmikronConnection { return; } - // ────────────────────────────── - // Challenge response - // ────────────────────────────── if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { let client_response = cv .get_data(DataTypes::challenge) @@ -726,79 +726,73 @@ impl OmikronConnection { // CHANGING DATA if cv.is_type(CommunicationType::change_user_data) { - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - let mut success = true; - let mut error_message = String::new(); + let user_id = cv.get_sender(); + let mut success = true; + let mut error_message = String::new(); - if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } + if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(public_key) = - cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) + } + if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { + if let Some(private_key_hash) = cv + .get_data(DataTypes::private_key_hash) + .and_then(|v| v.as_str()) { - if let Some(private_key_hash) = cv - .get_data(DataTypes::private_key_hash) - .and_then(|v| v.as_str()) - { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await - { - success = false; - error_message = e.to_string(); - } - } else { - success = false; - error_message = - "private_key_hash is required when changing public_key".to_string(); - } - } - - if success { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, error_message), + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), ) - .await; + .await + { + success = false; + error_message = e.to_string(); + } + } else { + success = false; + error_message = + "private_key_hash is required when changing public_key".to_string(); } + } + + if success { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, error_message), + ) + .await; } return; } From 5cf838ae5d245140774078fdea27dd8f5dd5bd86 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 20 Feb 2026 16:19:13 +0100 Subject: [PATCH 070/220] [Fix] Use sender ID for change_user_data --- src/server/omikron_connection.rs | 126 +++++++++++++++---------------- 1 file changed, 60 insertions(+), 66 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 11dc310..e2e9ca7 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -92,6 +92,9 @@ impl OmikronConnection { return; } + // ────────────────────────────── + // Identification + // ────────────────────────────── if !self.is_identified().await { let identified = *self.identified.read().await; let challenged = *self.challenged.read().await; @@ -165,9 +168,6 @@ impl OmikronConnection { return; } - // ────────────────────────────── - // Challenge response - // ────────────────────────────── if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { let client_response = cv .get_data(DataTypes::challenge) @@ -726,79 +726,73 @@ impl OmikronConnection { // CHANGING DATA if cv.is_type(CommunicationType::change_user_data) { - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - let mut success = true; - let mut error_message = String::new(); + let user_id = cv.get_sender(); + let mut success = true; + let mut error_message = String::new(); - if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } + if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } + } + if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); } - if let Some(public_key) = - cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) + } + if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { + if let Some(private_key_hash) = cv + .get_data(DataTypes::private_key_hash) + .and_then(|v| v.as_str()) { - if let Some(private_key_hash) = cv - .get_data(DataTypes::private_key_hash) - .and_then(|v| v.as_str()) - { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await - { - success = false; - error_message = e.to_string(); - } - } else { - success = false; - error_message = - "private_key_hash is required when changing public_key".to_string(); - } - } - - if success { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, error_message), + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), ) - .await; + .await + { + success = false; + error_message = e.to_string(); + } + } else { + success = false; + error_message = + "private_key_hash is required when changing public_key".to_string(); } + } + + if success { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, error_message), + ) + .await; } return; } From 2d66c26fee3cdd6d5dcd7277013bf4202ab050cf Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:17:41 +0100 Subject: [PATCH 071/220] [Fix] Omikron Veryfies users/Omikrons --- src/server/omikron_connection.rs | 91 ++++++++++++++------------------ 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e2e9ca7..53e85f3 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -797,8 +797,8 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::change_iota_data) { - if let (Some(user_id), Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), + if let (user_id, Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_sender(), cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), cv.get_data(DataTypes::new_token).and_then(|v| v.as_str()), @@ -854,25 +854,21 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::delete_user) { - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - match sql::delete_user(user_id).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; - } + let user_id = cv.get_sender(); + match sql::delete_user(user_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; } - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) .await; + } } return; } @@ -902,35 +898,29 @@ impl OmikronConnection { // NOTIFICATIONS if cv.is_type(CommunicationType::get_notifications) { - if let Some(JsonValue::Number(user_id)) = cv.get_data(DataTypes::user_id) { - if let Ok(notifications) = - sql::get_notifications(user_id.as_fixed_point_i64(0).unwrap()).await - { - let mut json_array = Vec::new(); - for (sender, amount) in notifications { - let mut obj = JsonValue::new_object(); - let _ = obj.insert("sender", JsonValue::from(sender)); - let _ = obj.insert("amount", JsonValue::from(amount)); - json_array.push(obj); - } - let response = CommunicationValue::new(CommunicationType::get_notifications) - .with_id(cv.get_id()) - .add_array(DataTypes::notifications, json_array); - self.send_message(&response).await; + let user_id = cv.get_sender(); + if let Ok(notifications) = sql::get_notifications(user_id).await { + let mut json_array = Vec::new(); + for (sender, amount) in notifications { + let mut obj = JsonValue::new_object(); + let _ = obj.insert("sender", JsonValue::from(sender)); + let _ = obj.insert("amount", JsonValue::from(amount)); + json_array.push(obj); } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_array(DataTypes::notifications, json_array); + self.send_message(&response).await; } } if cv.is_type(CommunicationType::read_notification) { - if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( - cv.get_data(DataTypes::receiver_id), - cv.get_data(DataTypes::sender_id), + if let (user_id, Some(other_id)) = ( + cv.get_sender(), + cv.get_data(DataTypes::sender_id) + .unwrap_or(&JsonValue::Null) + .as_i64(), ) { - if let Ok(_) = sql::read_notification( - user_id.as_fixed_point_i64(0).unwrap(), - other_id.as_fixed_point_i64(0).unwrap(), - ) - .await - { + if let Ok(_) = sql::read_notification(user_id, other_id).await { let response = CommunicationValue::new(CommunicationType::read_notification) .with_id(cv.get_id()); self.send_message(&response).await; @@ -938,16 +928,13 @@ impl OmikronConnection { } } if cv.is_type(CommunicationType::push_notification) { - if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( - cv.get_data(DataTypes::receiver_id), - cv.get_data(DataTypes::sender_id), + if let (user_id, Some(other_id)) = ( + cv.get_sender(), + cv.get_data(DataTypes::sender_id) + .unwrap_or(&JsonValue::Null) + .as_i64(), ) { - if let Ok(_) = sql::add_notification( - user_id.as_fixed_point_i64(0).unwrap(), - other_id.as_fixed_point_i64(0).unwrap(), - ) - .await - { + if let Ok(_) = sql::add_notification(user_id, other_id).await { let response = CommunicationValue::new(CommunicationType::push_notification) .with_id(cv.get_id()); self.send_message(&response).await; From d138484c99b78b32bc61969621d8271408e46ad7 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 20 Feb 2026 17:17:41 +0100 Subject: [PATCH 072/220] [Fix] Omikron Veryfies users/Omikrons --- src/server/omikron_connection.rs | 91 ++++++++++++++------------------ 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e2e9ca7..53e85f3 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -797,8 +797,8 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::change_iota_data) { - if let (Some(user_id), Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), + if let (user_id, Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_sender(), cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), cv.get_data(DataTypes::new_token).and_then(|v| v.as_str()), @@ -854,25 +854,21 @@ impl OmikronConnection { return; } if cv.is_type(CommunicationType::delete_user) { - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - match sql::delete_user(user_id).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; - } + let user_id = cv.get_sender(); + match sql::delete_user(user_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; } - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + Err(e) => { + self.send_message( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data_str(DataTypes::error_type, e.to_string()), + ) .await; + } } return; } @@ -902,35 +898,29 @@ impl OmikronConnection { // NOTIFICATIONS if cv.is_type(CommunicationType::get_notifications) { - if let Some(JsonValue::Number(user_id)) = cv.get_data(DataTypes::user_id) { - if let Ok(notifications) = - sql::get_notifications(user_id.as_fixed_point_i64(0).unwrap()).await - { - let mut json_array = Vec::new(); - for (sender, amount) in notifications { - let mut obj = JsonValue::new_object(); - let _ = obj.insert("sender", JsonValue::from(sender)); - let _ = obj.insert("amount", JsonValue::from(amount)); - json_array.push(obj); - } - let response = CommunicationValue::new(CommunicationType::get_notifications) - .with_id(cv.get_id()) - .add_array(DataTypes::notifications, json_array); - self.send_message(&response).await; + let user_id = cv.get_sender(); + if let Ok(notifications) = sql::get_notifications(user_id).await { + let mut json_array = Vec::new(); + for (sender, amount) in notifications { + let mut obj = JsonValue::new_object(); + let _ = obj.insert("sender", JsonValue::from(sender)); + let _ = obj.insert("amount", JsonValue::from(amount)); + json_array.push(obj); } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_array(DataTypes::notifications, json_array); + self.send_message(&response).await; } } if cv.is_type(CommunicationType::read_notification) { - if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( - cv.get_data(DataTypes::receiver_id), - cv.get_data(DataTypes::sender_id), + if let (user_id, Some(other_id)) = ( + cv.get_sender(), + cv.get_data(DataTypes::sender_id) + .unwrap_or(&JsonValue::Null) + .as_i64(), ) { - if let Ok(_) = sql::read_notification( - user_id.as_fixed_point_i64(0).unwrap(), - other_id.as_fixed_point_i64(0).unwrap(), - ) - .await - { + if let Ok(_) = sql::read_notification(user_id, other_id).await { let response = CommunicationValue::new(CommunicationType::read_notification) .with_id(cv.get_id()); self.send_message(&response).await; @@ -938,16 +928,13 @@ impl OmikronConnection { } } if cv.is_type(CommunicationType::push_notification) { - if let (Some(JsonValue::Number(user_id)), Some(JsonValue::Number(other_id))) = ( - cv.get_data(DataTypes::receiver_id), - cv.get_data(DataTypes::sender_id), + if let (user_id, Some(other_id)) = ( + cv.get_sender(), + cv.get_data(DataTypes::sender_id) + .unwrap_or(&JsonValue::Null) + .as_i64(), ) { - if let Ok(_) = sql::add_notification( - user_id.as_fixed_point_i64(0).unwrap(), - other_id.as_fixed_point_i64(0).unwrap(), - ) - .await - { + if let Ok(_) = sql::add_notification(user_id, other_id).await { let response = CommunicationValue::new(CommunicationType::push_notification) .with_id(cv.get_id()); self.send_message(&response).await; From f5e7c9b3b05343c6ca750a3d546475c93c465981 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:14:59 +0100 Subject: [PATCH 073/220] [Add] Logging --- src/server/omikron_connection.rs | 20 +------ src/util/logger.rs | 99 ++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 53e85f3..08a2ead 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -7,7 +7,7 @@ use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_in, log_out}; +use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in, log_out}; use actix::Addr; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; @@ -50,14 +50,7 @@ impl OmikronConnection { pub async fn send_message(&self, cv: &CommunicationValue) { let text = cv.to_json().to_string(); - if !cv.is_type(CommunicationType::pong) { - log_out!( - *self.omikron_id.read().await, - PrintType::Omikron, - "{}", - text - ); - } + log_cv_out!(PrintType::Omikron, cv); self.ws_addr.read().await.do_send(WsSendMessage(text)); } @@ -75,18 +68,11 @@ impl OmikronConnection { pub async fn handle_message(self: Arc, message: String) { let cv = CommunicationValue::from_json(&message); + log_cv_in!(PrintType::Omikron, cv); if cv.is_type(CommunicationType::ping) { self.handle_ping(cv).await; return; } - - log_in!( - *self.omikron_id.read().await, - PrintType::Omikron, - "{}", - message - ); - if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { let _ = task(self.clone(), cv.clone()); return; diff --git a/src/util/logger.rs b/src/util/logger.rs index 714ee53..80bc7c6 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -8,6 +8,9 @@ use std::{ }; use ansi_term::Color; +use json::JsonValue; + +use crate::data::communication::CommunicationValue; static LOGGER: OnceLock> = OnceLock::new(); @@ -31,8 +34,6 @@ struct LogMessage { message: String, } -/// Initialize the logging subsystem. -/// Must be called exactly once during startup. pub fn startup() { let (tx, rx) = mpsc::channel::(); LOGGER.set(tx).expect("Logger already initialized"); @@ -62,10 +63,8 @@ pub fn startup() { let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); - // Console (ANSI-colored) println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); - // File (plain text) let _ = writeln!(file, "{}", line); } }); @@ -96,9 +95,6 @@ fn fixed_box(content: &str, width: usize) -> String { } } -/** Internal async logging entry point. -* Not exposed publicly; all access goes through macros. -*/ pub fn log_internal( sender: Option, kind: PrintType, @@ -120,7 +116,7 @@ pub fn log_internal( }); } } -/// Log a general informational message. + #[macro_export] macro_rules! log { // plain @@ -144,7 +140,7 @@ macro_rules! log { $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) }; } -/// Log an inbound message (`>`). + #[macro_export] macro_rules! log_in { // sender + actor @@ -168,7 +164,7 @@ macro_rules! log_in { ) }; } -/// Log an outbound message (`<`). + #[macro_export] macro_rules! log_out { @@ -193,7 +189,7 @@ macro_rules! log_out { ) }; } -/// Log an error message (`>>`). + #[macro_export] macro_rules! log_err { @@ -218,3 +214,84 @@ macro_rules! log_err { ) }; } + +// ******** COMMUNICATION VALUES ******** +pub fn log_cv_internal( + prefix: &'static str, + cv: &CommunicationValue, + print_type: Option, +) { + let formatted = format_cv(cv); + + log_internal( + Some(cv.get_sender()), + print_type.unwrap_or(PrintType::General), + prefix, + false, + formatted, + ); +} + +pub fn format_cv(cv: &CommunicationValue) -> String { + let mut parts = Vec::new(); + + let sender = cv.get_sender(); + let receiver = cv.get_receiver(); + + if sender > 0 && receiver > 0 { + parts.push(format!("{} > {}", sender, receiver)); + } else if sender > 0 { + parts.push(format!("{}", sender)); + } else if receiver > 0 { + parts.push(format!("> {}", receiver)); + } + + let comm_type = cv.get_type().to_string(); + parts.push(format!("{}", comm_type)); + + let mut data_parts = Vec::new(); + if let JsonValue::Object(data) = &cv.clone().to_json()["data"] { + for (key, value) in data.iter() { + let val_string = match value { + JsonValue::String(s) => s.clone(), + _ => value.dump(), + }; + + data_parts.push(format!("{} {}", key, val_string)); + } + } + + if !data_parts.is_empty() { + parts.push(format!("{}", data_parts.join(", "))); + } + + parts.join(": ") +} +#[macro_export] +macro_rules! log_cv { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, None) + }; +} + +#[macro_export] +macro_rules! log_cv_in { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, None) + }; +} +#[macro_export] +macro_rules! log_cv_out { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, None) + }; +} From d22367feea9fe6fff9afd65c0e3293d1a9764f89 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 21 Feb 2026 11:14:59 +0100 Subject: [PATCH 074/220] [Add] Logging --- src/server/omikron_connection.rs | 20 +------ src/util/logger.rs | 99 ++++++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 28 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 53e85f3..08a2ead 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -7,7 +7,7 @@ use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get use crate::sql::user_online_tracker::{self}; use crate::util::crypto_helper::encrypt; use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_in, log_out}; +use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in, log_out}; use actix::Addr; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; @@ -50,14 +50,7 @@ impl OmikronConnection { pub async fn send_message(&self, cv: &CommunicationValue) { let text = cv.to_json().to_string(); - if !cv.is_type(CommunicationType::pong) { - log_out!( - *self.omikron_id.read().await, - PrintType::Omikron, - "{}", - text - ); - } + log_cv_out!(PrintType::Omikron, cv); self.ws_addr.read().await.do_send(WsSendMessage(text)); } @@ -75,18 +68,11 @@ impl OmikronConnection { pub async fn handle_message(self: Arc, message: String) { let cv = CommunicationValue::from_json(&message); + log_cv_in!(PrintType::Omikron, cv); if cv.is_type(CommunicationType::ping) { self.handle_ping(cv).await; return; } - - log_in!( - *self.omikron_id.read().await, - PrintType::Omikron, - "{}", - message - ); - if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { let _ = task(self.clone(), cv.clone()); return; diff --git a/src/util/logger.rs b/src/util/logger.rs index 714ee53..80bc7c6 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -8,6 +8,9 @@ use std::{ }; use ansi_term::Color; +use json::JsonValue; + +use crate::data::communication::CommunicationValue; static LOGGER: OnceLock> = OnceLock::new(); @@ -31,8 +34,6 @@ struct LogMessage { message: String, } -/// Initialize the logging subsystem. -/// Must be called exactly once during startup. pub fn startup() { let (tx, rx) = mpsc::channel::(); LOGGER.set(tx).expect("Logger already initialized"); @@ -62,10 +63,8 @@ pub fn startup() { let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); - // Console (ANSI-colored) println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); - // File (plain text) let _ = writeln!(file, "{}", line); } }); @@ -96,9 +95,6 @@ fn fixed_box(content: &str, width: usize) -> String { } } -/** Internal async logging entry point. -* Not exposed publicly; all access goes through macros. -*/ pub fn log_internal( sender: Option, kind: PrintType, @@ -120,7 +116,7 @@ pub fn log_internal( }); } } -/// Log a general informational message. + #[macro_export] macro_rules! log { // plain @@ -144,7 +140,7 @@ macro_rules! log { $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) }; } -/// Log an inbound message (`>`). + #[macro_export] macro_rules! log_in { // sender + actor @@ -168,7 +164,7 @@ macro_rules! log_in { ) }; } -/// Log an outbound message (`<`). + #[macro_export] macro_rules! log_out { @@ -193,7 +189,7 @@ macro_rules! log_out { ) }; } -/// Log an error message (`>>`). + #[macro_export] macro_rules! log_err { @@ -218,3 +214,84 @@ macro_rules! log_err { ) }; } + +// ******** COMMUNICATION VALUES ******** +pub fn log_cv_internal( + prefix: &'static str, + cv: &CommunicationValue, + print_type: Option, +) { + let formatted = format_cv(cv); + + log_internal( + Some(cv.get_sender()), + print_type.unwrap_or(PrintType::General), + prefix, + false, + formatted, + ); +} + +pub fn format_cv(cv: &CommunicationValue) -> String { + let mut parts = Vec::new(); + + let sender = cv.get_sender(); + let receiver = cv.get_receiver(); + + if sender > 0 && receiver > 0 { + parts.push(format!("{} > {}", sender, receiver)); + } else if sender > 0 { + parts.push(format!("{}", sender)); + } else if receiver > 0 { + parts.push(format!("> {}", receiver)); + } + + let comm_type = cv.get_type().to_string(); + parts.push(format!("{}", comm_type)); + + let mut data_parts = Vec::new(); + if let JsonValue::Object(data) = &cv.clone().to_json()["data"] { + for (key, value) in data.iter() { + let val_string = match value { + JsonValue::String(s) => s.clone(), + _ => value.dump(), + }; + + data_parts.push(format!("{} {}", key, val_string)); + } + } + + if !data_parts.is_empty() { + parts.push(format!("{}", data_parts.join(", "))); + } + + parts.join(": ") +} +#[macro_export] +macro_rules! log_cv { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, None) + }; +} + +#[macro_export] +macro_rules! log_cv_in { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, None) + }; +} +#[macro_export] +macro_rules! log_cv_out { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, None) + }; +} From 0c9ec2c021efc9d361c2e6a054e352e3b233d2ee Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 25 Feb 2026 09:08:24 +0100 Subject: [PATCH 075/220] [Fix] (stable) Omikron - Omega connection, --- src/server/omikron_connection.rs | 24 ++++++++++------------- src/server/omikron_manager.rs | 25 +++++++++++++++--------- src/sql/user_online_tracker.rs | 33 ++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f1634fc..9ddbb5b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -958,23 +958,19 @@ impl OmikronConnection { self.send_message(&error).await; } pub async fn close(&self) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } + let omikron_id = self.get_omikron_id().await; + if omikron_id != 0 { + log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; } } pub async fn handle_close(self: Arc) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } + let omikron_id = self.get_omikron_id().await; + if omikron_id != 0 { + log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; } } diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index 864c9d6..f481702 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -1,4 +1,4 @@ -use crate::{log_in, server::omikron_connection::OmikronConnection, util::logger::PrintType}; +use crate::server::omikron_connection::OmikronConnection; use dashmap::DashMap; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; @@ -7,20 +7,27 @@ use std::sync::Arc; pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); -pub async fn add_omikron(omikron_conn: Arc) { - OMIKRON_CONNECTIONS.insert(omikron_conn.get_omikron_id().await, omikron_conn); +pub async fn add_omikron(conn: Arc) { + let id = conn.get_omikron_id().await; + + if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { + old.close().await; + } } pub async fn remove_omikron(omikron_id: i64) { OMIKRON_CONNECTIONS.remove(&omikron_id); } - pub async fn get_random_omikron() -> Result, ()> { - log_in!(0, PrintType::Iota, "{}", OMIKRON_CONNECTIONS.len()); let mut rng = rand::thread_rng(); - if let Some((_, val)) = OMIKRON_CONNECTIONS.clone().into_iter().choose(&mut rng) { - return Ok(val); - } else { - return Err(()); + + let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); + + if let Some(key) = keys.into_iter().choose(&mut rng) { + if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { + return Ok(entry.clone()); + } } + + Err(()) } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 8cde17b..eee1700 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -81,26 +81,31 @@ pub fn untrack_many_users(user_ids: &[i64]) { } pub async fn untrack_omikron(omikron_id: i64) { + let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION + .iter() + .filter(|entry| *entry.value() == omikron_id) + .map(|entry| *entry.key()) + .collect(); + + for key in primary_keys_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); + } + let mut offline_iotas = Vec::new(); - IOTA_OMIKRON_CONNECTIONS.retain(|iota_id, connections| { + for mut entry in IOTA_OMIKRON_CONNECTIONS.iter_mut() { + let connections = entry.value_mut(); + connections.retain(|id| *id != omikron_id); - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) - { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); - } - if connections.is_empty() { - offline_iotas.push(*iota_id); - false - } else { - true + offline_iotas.push(*entry.key()); } - }); + } + + for iota_id in &offline_iotas { + IOTA_OMIKRON_CONNECTIONS.remove(iota_id); + } USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); From 2f13aac0509cf136b84923a6f88fc7c6b2365e6f Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 25 Feb 2026 09:08:24 +0100 Subject: [PATCH 076/220] [Fix] (stable) Omikron - Omega connection, --- src/server/omikron_connection.rs | 24 ++++++++++------------- src/server/omikron_manager.rs | 25 +++++++++++++++--------- src/sql/user_online_tracker.rs | 33 ++++++++++++++++++-------------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index f1634fc..9ddbb5b 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -958,23 +958,19 @@ impl OmikronConnection { self.send_message(&error).await; } pub async fn close(&self) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } + let omikron_id = self.get_omikron_id().await; + if omikron_id != 0 { + log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; } } pub async fn handle_close(self: Arc) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } + let omikron_id = self.get_omikron_id().await; + if omikron_id != 0 { + log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; } } diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index 864c9d6..f481702 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -1,4 +1,4 @@ -use crate::{log_in, server::omikron_connection::OmikronConnection, util::logger::PrintType}; +use crate::server::omikron_connection::OmikronConnection; use dashmap::DashMap; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; @@ -7,20 +7,27 @@ use std::sync::Arc; pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); -pub async fn add_omikron(omikron_conn: Arc) { - OMIKRON_CONNECTIONS.insert(omikron_conn.get_omikron_id().await, omikron_conn); +pub async fn add_omikron(conn: Arc) { + let id = conn.get_omikron_id().await; + + if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { + old.close().await; + } } pub async fn remove_omikron(omikron_id: i64) { OMIKRON_CONNECTIONS.remove(&omikron_id); } - pub async fn get_random_omikron() -> Result, ()> { - log_in!(0, PrintType::Iota, "{}", OMIKRON_CONNECTIONS.len()); let mut rng = rand::thread_rng(); - if let Some((_, val)) = OMIKRON_CONNECTIONS.clone().into_iter().choose(&mut rng) { - return Ok(val); - } else { - return Err(()); + + let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); + + if let Some(key) = keys.into_iter().choose(&mut rng) { + if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { + return Ok(entry.clone()); + } } + + Err(()) } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 8cde17b..eee1700 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -81,26 +81,31 @@ pub fn untrack_many_users(user_ids: &[i64]) { } pub async fn untrack_omikron(omikron_id: i64) { + let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION + .iter() + .filter(|entry| *entry.value() == omikron_id) + .map(|entry| *entry.key()) + .collect(); + + for key in primary_keys_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); + } + let mut offline_iotas = Vec::new(); - IOTA_OMIKRON_CONNECTIONS.retain(|iota_id, connections| { + for mut entry in IOTA_OMIKRON_CONNECTIONS.iter_mut() { + let connections = entry.value_mut(); + connections.retain(|id| *id != omikron_id); - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) - { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); - } - if connections.is_empty() { - offline_iotas.push(*iota_id); - false - } else { - true + offline_iotas.push(*entry.key()); } - }); + } + + for iota_id in &offline_iotas { + IOTA_OMIKRON_CONNECTIONS.remove(iota_id); + } USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); From a0ae3786010a572291d1684d9b28d918535f0cbd Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:45:22 +0100 Subject: [PATCH 077/220] [FIX] Migrated to Tensamin Transport Protocol --- Cargo.lock | 267 +++++--- Cargo.toml | 8 +- src/data/communication.rs | 403 ----------- src/data/mod.rs | 1 - src/main.rs | 6 +- src/server/api.rs | 234 ++++--- src/server/mod.rs | 1 - src/server/omikron_connection.rs | 1068 +++++++----------------------- src/server/server.rs | 14 +- src/server/socket.rs | 129 ---- src/util/logger.rs | 87 ++- 11 files changed, 626 insertions(+), 1592 deletions(-) delete mode 100644 src/data/communication.rs delete mode 100644 src/data/mod.rs delete mode 100755 src/server/socket.rs diff --git a/Cargo.lock b/Cargo.lock index 7469a5e..40c314f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,8 @@ dependencies = [ "color-eyre", "dashmap", "dotenv", + "epsilon-core", + "epsilon-native", "futures", "futures-util", "hex", @@ -32,16 +34,17 @@ dependencies = [ "json", "once_cell", "pnet", + "quinn", "rand 0.8.5", "rand_core 0.6.4", "reqwest", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pemfile 2.2.0", "sha1", "sha2", "sqlx", - "strum", - "strum_macros", + "strum 0.27.2", + "strum_macros 0.27.2", "sysinfo", "tokio", "tokio-rustls", @@ -99,9 +102,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.2" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" dependencies = [ "actix-codec", "actix-rt", @@ -149,9 +152,9 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" dependencies = [ "bytestring", "cfg-if", @@ -231,9 +234,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.12.1" +version = "4.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" dependencies = [ "actix-codec", "actix-http", @@ -405,9 +408,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" @@ -443,9 +446,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -503,7 +506,7 @@ dependencies = [ "futures-lite 2.6.1", "parking", "polling 3.11.0", - "rustix 1.1.3", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -649,9 +652,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -817,9 +820,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -1113,9 +1116,9 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deflate64" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" [[package]] name = "der" @@ -1130,9 +1133,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -1241,6 +1244,32 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "epsilon-core" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +dependencies = [ + "byteorder", + "rand 0.8.5", + "strum 0.28.0", + "strum_macros 0.28.0", +] + +[[package]] +name = "epsilon-native" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "epsilon-core", + "quinn", + "rustls 0.23.37", + "thiserror 2.0.18", + "tokio", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1305,6 +1334,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1398,9 +1439,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1413,9 +1454,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1423,15 +1464,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1451,9 +1492,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -1485,9 +1526,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1496,21 +1537,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1520,7 +1561,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1903,7 +1943,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -2163,9 +2203,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -2233,7 +2273,7 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.11.0", "libc", - "redox_syscall 0.7.1", + "redox_syscall 0.7.3", ] [[package]] @@ -2254,9 +2294,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -2367,9 +2407,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -2535,9 +2575,9 @@ dependencies = [ [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "parking" @@ -2595,9 +2635,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -2760,7 +2800,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.5.2", "pin-project-lite", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2846,7 +2886,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.36", + "rustls 0.23.37", "socket2 0.6.2", "thiserror 2.0.18", "tokio", @@ -2862,13 +2902,15 @@ checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "aws-lc-rs", "bytes", + "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pki-types", + "rustls-platform-verifier", "slab", "thiserror 2.0.18", "tinyvec", @@ -2981,9 +3023,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ "bitflags 2.11.0", ] @@ -3019,9 +3061,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -3046,7 +3088,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pki-types", "rustls-platform-verifier", "sync_wrapper", @@ -3132,14 +3174,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -3157,13 +3199,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.103.9", "subtle", @@ -3221,7 +3264,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.9", @@ -3307,9 +3350,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.11.0", "core-foundation 0.10.1", @@ -3320,9 +3363,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3472,6 +3515,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "slab" version = "0.4.12" @@ -3747,6 +3796,12 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + [[package]] name = "strum_macros" version = "0.27.2" @@ -3759,6 +3814,18 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3767,9 +3834,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.115" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3852,14 +3919,14 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand 2.3.0", "getrandom 0.4.1", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -4023,7 +4090,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.36", + "rustls 0.23.37", "tokio", ] @@ -4094,9 +4161,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -4226,7 +4293,7 @@ dependencies = [ "log", "native-tls", "rand 0.9.2", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pki-types", "sha1", "thiserror 2.0.18", @@ -4248,9 +4315,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -4417,9 +4484,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -4430,9 +4497,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -4444,9 +4511,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4454,9 +4521,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -4467,9 +4534,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -4510,9 +4577,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -5179,18 +5246,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -5300,9 +5367,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" +checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 27766b7..8432d26 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] +epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } +epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } + actix = "0.13.5" actix-rt = "2.11.0" actix-web = { version = "4.12.1", features = ["rustls-0_23"] } @@ -44,7 +47,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = { version = "0.8.8", features = [ "ws", "http2" ] } +axum = { version = "0.8.8", features = ["ws", "http2"] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" @@ -64,7 +67,7 @@ pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } reqwest = "0.13.2" -rustls = "0.23.36" +rustls = "0.23.37" rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" @@ -82,3 +85,4 @@ uuid = { version = "1.19.0", features = ["v4"] } walkdir = "2.5.0" x448 = "0.6.0" zip = "6.0.0" +quinn = "0.11.9" diff --git a/src/data/communication.rs b/src/data/communication.rs deleted file mode 100644 index 99229f5..0000000 --- a/src/data/communication.rs +++ /dev/null @@ -1,403 +0,0 @@ -use json::number::Number; -use json::{Array, JsonValue, object, parse}; -use std::collections::HashMap; -use std::time::{SystemTime, UNIX_EPOCH}; -use strum::IntoEnumIterator; -use strum_macros::EnumIter; -use uuid::Uuid; - -#[derive(Eq, Hash, PartialEq, EnumIter, Clone, Debug)] -#[allow(non_camel_case_types, dead_code)] -pub enum DataTypes { - error_type, - accepted_ids, - uuid, - register_id, - - link, - - settings, - settings_name, - chat_partner_id, - chat_partner_name, - iota_id, - user_id, - user_ids, - iota_ids, - user_state, - user_states, - user_pings, - call_state, - screen_share, - private_key_hash, - accepted, - accepted_profiles, - denied_profiles, - content, - messages, - notifications, - send_time, - get_time, - get_variant, - shared_secret_own, - shared_secret_other, - shared_secret_sign, - shared_secret, - call_id, - call_token, - untill, - enabled, - start_date, - end_date, - receiver_id, - sender_id, - signature, - signed, - message, - message_state, - 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, - rho_connections, - user, - online_status, - omikron_id, - omikron_connections, - reset_token, - new_token, -} - -impl DataTypes { - pub fn parse(p0: String) -> DataTypes { - for datatype in DataTypes::iter() { - if datatype.to_string().to_lowercase().replace('_', "") - == p0.to_lowercase().replace('_', "") - { - return datatype; - } - } - DataTypes::error_type - } - pub fn to_string(&self) -> String { - return format!("{:?}", self); - } -} - -#[derive(PartialEq, Clone, EnumIter, Debug)] -#[allow(non_camel_case_types, dead_code)] -pub enum CommunicationType { - error, - error_anonymous, - error_internal, - error_invalid_data, - error_invalid_user_id, - error_invalid_omikron_id, - error_not_found, - error_not_authenticated, - error_no_iota, - error_invalid_challenge, - error_invalid_secret, - error_invalid_private_key, - error_invalid_public_key, - error_no_user_id, - error_no_call_id, - error_invalid_call_id, - success, - - shorten_link, - - settings_save, - settings_load, - settings_list, - message, - message_state, - message_send, - message_live, - message_other_iota, - message_chunk, - messages_get, - - push_notification, - read_notification, - get_notifications, - - 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, - register_iota, - register_iota_success, - ping, - pong, - add_conversation, - send_chat, - client_changed, - client_connected, - client_disconnected, - client_closed, - public_key, - private_key, - webrtc_sdp, - webrtc_ice, - start_stream, - end_stream, - watch_stream, - call_token, - call_invite, - call_disconnect_user, - call_timeout_user, - call_set_anonymous_joining, - end_call, - function, - update, - create_user, - rho_update, - - user_connected, - user_disconnected, - iota_connected, - iota_disconnected, - sync_client_iota_status, - - get_user_data, - get_iota_data, - iota_user_data, - - change_user_data, - change_iota_data, - - get_register, - complete_register_user, - complete_register_iota, - delete_user, - delete_iota, - - start_register, - complete_register, -} -impl CommunicationType { - pub fn parse(p0: String) -> CommunicationType { - for datatype in CommunicationType::iter() { - if datatype.to_string().to_lowercase().replace('_', "") - == p0.to_lowercase().replace('_', "") - { - return datatype; - } - } - CommunicationType::error - } - pub fn to_string(&self) -> String { - return format!("{:?}", self); - } -} - -#[derive(Debug, Clone)] -pub struct CommunicationValue { - id: Uuid, - comm_type: CommunicationType, - sender: i64, - receiver: i64, - data: HashMap, -} - -#[allow(dead_code)] -impl CommunicationValue { - pub fn new(comm_type: CommunicationType) -> Self { - Self { - id: Uuid::new_v4(), - comm_type, - sender: 0, - receiver: 0, - 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: i64) -> Self { - self.sender = sender; - self - } - pub fn get_sender(&self) -> i64 { - self.sender.clone() - } - pub fn with_receiver(mut self, receiver: i64) -> Self { - self.receiver = receiver; - self - } - pub fn get_receiver(&self) -> i64 { - 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 fn get_type(&self) -> CommunicationType { - self.comm_type.clone() - } - pub 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()); - } - if self.sender > 0 && self.receiver > 0 { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - sender: self.sender, - receiver: self.receiver, - data: jdata - } - } else if self.sender > 0 { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - sender: self.sender, - data: jdata - } - } else if self.receiver > 0 { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - receiver: self.receiver, - data: jdata - } - } else { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - data: jdata - } - } - } - - pub fn from_json(json_str: &str) -> Self { - if let Ok(parsed) = parse(json_str) { - let comm_type = CommunicationType::parse(parsed["type"].to_string()); - let mut sender: i64 = 0; - if parsed.has_key("sender") { - sender = parsed["sender"].as_i64().unwrap_or(0); - } - let mut receiver: i64 = 0; - if parsed.has_key("receiver") { - receiver = parsed["receiver"].as_i64().unwrap_or(0); - } - - 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, - } - } else { - Self { - id: Uuid::new_v4(), - comm_type: CommunicationType::error, - sender: 0, - receiver: 0, - data: HashMap::new(), - } - } - } - pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue { - let receiver = original - .get_data(DataTypes::receiver_id) - .unwrap_or(&JsonValue::Number(Number::from(0))) - .as_i64() - .unwrap_or(0); - - let now_ms = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as i64; - - let sender = original.get_sender(); - CommunicationValue::new(CommunicationType::message_other_iota) - .with_id(original.get_id()) - .with_receiver(receiver) - .add_data( - DataTypes::receiver_id, - JsonValue::Number(Number::from(receiver)), - ) - .with_sender(sender) - .add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string())) - .add_data( - DataTypes::sender_id, - JsonValue::Number(Number::from(sender)), - ) - .add_data( - DataTypes::content, - JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()), - ) - } -} diff --git a/src/data/mod.rs b/src/data/mod.rs deleted file mode 100644 index 73c2657..0000000 --- a/src/data/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod communication; diff --git a/src/main.rs b/src/main.rs index 88637e6..043700c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ -mod data; mod server; mod sql; mod util; +use crate::server::omikron_connection; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::util::crypto_helper::load_public_key; @@ -43,7 +43,7 @@ async fn main() { } else { log!(" Users"); } - let _ = server::server::start(9187).await; - + let _ = server::server::start(9188).await; + let _ = omikron_connection::OmikronServer::start(9187).await; tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/api.rs b/src/server/api.rs index f826e3e..7f3c728 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,4 +1,3 @@ -use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; @@ -11,7 +10,6 @@ use actix_web::HttpResponse; use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; -use json::number::Number; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { if path == "OPTIONS" { @@ -35,6 +33,9 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { }; let (status, body_text) = match path_parts.as_slice() { + // ================================================== + // DOWNLOAD IOTA FRONTEND + // ================================================== ["api", "download", "iota_frontend"] => { let file_path = "downloads/[iota_frontend].zip"; @@ -50,87 +51,107 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { .body(file_bytes); } Err(_) => { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); return HttpResponse::NotFound() .insert_header(("Access-Control-Allow-Origin", "*")) - .body("File not found"); + .body(res.dump()); } } } + + // ================================================== + // GET RANDOM OMIKRON + // ================================================== ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { - if let Ok((public_key, ip_address)) = - sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_conn.get_omikron_id().await, - public_key, - ip_address - ), - ) + let id = omikron_conn.get_omikron_id().await; + + if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + + (StatusCode::OK, res.dump()) } else { - ( - StatusCode::INTERNAL_SERVER_ERROR, - "selected an invalid omikron".to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) } } else { - ( - StatusCode::NOT_FOUND, - "couldn't find online omikron".to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } + + // ================================================== + // GET OMIKRON BY ID + // ================================================== ["api", "get", "omikron", id] => { let id = id.parse::().unwrap_or(0); + if id == 0 { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - id, public_key, ip_address - ), - ) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await { if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } + + // ================================================== + // GET ID BY USERNAME + // ================================================== ["api", "get", "id", username] => { if username.is_empty() { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) } else if let Ok(( id, iota_id, @@ -146,37 +167,49 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { _, )) = sql::get_by_username(username).await { - let cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); - (StatusCode::OK, cv.to_json().to_string()) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + (StatusCode::OK, res.dump()) } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) } } - ["api", "get", "public_key"] => (StatusCode::OK, public_key_to_base64(&get_public_key())), + + // ================================================== + // GET SERVER PUBLIC KEY + // ================================================== + ["api", "get", "public_key"] => { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["public_key"] = public_key_to_base64(&get_public_key()).into(); + (StatusCode::OK, res.dump()) + } + + // ================================================== + // GET USER BY ID + // ================================================== ["api", "get", "user", id] => { let id: i64 = id.parse().unwrap_or(0); + if id == 0 { - bad_request() + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) } else if let Ok(( id, iota_id, username, display, - status, + status_msg, about, avatar, sub_level, @@ -186,47 +219,46 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { _, )) = sql::get_by_user_id(id).await { - let mut cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + if let Some(display) = display { - cv = cv.add_data_str(DataTypes::display, display); + res["display"] = display.into(); } - if let Some(status) = status { - cv = cv.add_data_str(DataTypes::status, status); + if let Some(status_msg) = status_msg { + res["status_message"] = status_msg.into(); } if let Some(about) = about { - cv = cv.add_data_str(DataTypes::about, about); + res["about"] = about.into(); } if let Some(avatar) = avatar { - cv = cv.add_data_str( - DataTypes::avatar, - base64::engine::general_purpose::STANDARD.encode(avatar), - ); + res["avatar"] = base64::engine::general_purpose::STANDARD + .encode(avatar) + .into(); } - (StatusCode::OK, cv.to_json().to_string()) + + (StatusCode::OK, res.dump()) } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) } } - _ => ( - StatusCode::INTERNAL_SERVER_ERROR, - CommunicationValue::new(CommunicationType::error) - .to_json() - .to_string(), - ), + + // ================================================== + // DEFAULT + // ================================================== + _ => { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } }; let body_bytes = body_text.into_bytes(); diff --git a/src/server/mod.rs b/src/server/mod.rs index 72ff9f4..aab6ab6 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -3,4 +3,3 @@ pub mod omikron_connection; pub mod omikron_manager; pub mod server; pub mod short_link; -pub mod socket; diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 08a2ead..0ae49b7 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,43 +1,98 @@ -use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::server::omikron_manager; -use crate::server::short_link::add_short_link; -use crate::server::socket::{WsSendMessage, WsSession}; -use crate::sql::connection_status::UserStatus; -use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; -use crate::sql::user_online_tracker::{self}; -use crate::util::crypto_helper::encrypt; -use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in, log_out}; -use actix::Addr; +use crate::server::{omikron_manager, short_link::add_short_link}; +use crate::sql::{sql, sql::get_omikron_by_id, user_online_tracker}; +use crate::util::file_util::load_file_buf; +use crate::util::{crypto_helper::encrypt, logger::PrintType}; +use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use json::JsonValue; -use json::number::Number; -use rand::Rng; -use rand::distributions::Alphanumeric; +use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use epsilon_native::{Receiver, Sender, host}; +use quinn::ServerConfig; +use rand::{Rng, distributions::Alphanumeric}; +use rustls::pki_types::PrivateKeyDer; use std::sync::Arc; use tokio::sync::RwLock; -use uuid::Uuid; use x448::PublicKey; +pub struct OmikronServer; + +impl OmikronServer { + pub async fn start(port: u16) -> Result<(), Box> { + let tls_cfg = OmikronServer::load_tls().expect("TLS config failed"); + let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg) + .expect("Failed to convert to QuicServerConfig"); + let mut host = host(port, ServerConfig::with_crypto(Arc::new(server_crypto))).await?; + tokio::spawn(async move { + while let Some((sender, receiver)) = host.next().await { + let connection = OmikronConnection::new(sender); + tokio::spawn(Self::connection_loop(connection, receiver)); + } + }); + + Ok(()) + } + fn load_tls() -> Option { + let mut cert_file_buf = load_file_buf("certs", "cert.pem").ok()?; + let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; + + let cert_chain = rustls_pemfile::certs(&mut cert_file_buf) + .collect::, _>>() + .ok()?; + + let mut keys: Vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_buf) + .map(|k| k.map(Into::into)) + .collect::, _>>() + .ok()?; + + if keys.is_empty() { + let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; + keys = rustls_pemfile::rsa_private_keys(&mut key_file_buf) + .map(|k| k.map(Into::into)) + .collect::, _>>() + .ok()?; + } + + if keys.is_empty() { + return None; + } + + let cfg = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, keys.remove(0)) + .ok()?; + + Some(cfg) + } + + async fn connection_loop(conn: Arc, receiver: Receiver) { + while let Ok(cv) = receiver.receive().await { + log_cv_in!(PrintType::Omikron, cv); + conn.clone().handle_message(cv).await; + } + + conn.handle_close().await; + } +} + pub struct OmikronConnection { - pub ws_addr: Arc>>, - pub omikron_id: Arc>, - pub pub_key: Arc>>>, + sender: Arc>>, + omikron_id: Arc>, + pub_key: Arc>>>, + identified: Arc>, challenged: Arc>, challenge: Arc>, - pub ping: Arc>, - waiting_tasks: DashMap< - Uuid, - Box, CommunicationValue) -> bool + Send + Sync>, - >, + + ping: Arc>, + + waiting_tasks: + DashMap, CommunicationValue) -> bool + Send + Sync>>, } impl OmikronConnection { - pub fn new(ws_addr: Addr) -> Arc { + pub fn new(sender: Sender) -> Arc { Arc::new(Self { - ws_addr: Arc::new(RwLock::new(ws_addr)), + sender: Arc::new(RwLock::new(Some(sender))), omikron_id: Arc::new(RwLock::new(0)), pub_key: Arc::new(RwLock::new(None)), identified: Arc::new(RwLock::new(false)), @@ -47,836 +102,220 @@ impl OmikronConnection { waiting_tasks: DashMap::new(), }) } - pub async fn send_message(&self, cv: &CommunicationValue) { - let text = cv.to_json().to_string(); + async fn send(&self, cv: &CommunicationValue) { log_cv_out!(PrintType::Omikron, cv); - self.ws_addr.read().await.do_send(WsSendMessage(text)); + if let Some(sender) = self.sender.read().await.as_ref() { + if sender.send(cv).await.is_err() { + self.handle_close().await; + } + } + } + + async fn send_error_response(&self, id: u32, comm_type: CommunicationType) { + let response = CommunicationValue::new(comm_type).with_id(id); + self.send(&response).await; } pub async fn get_omikron_id(&self) -> i64 { - let omikron_id = { - let guard = self.omikron_id.read().await; - guard.clone() - }; - omikron_id + *self.omikron_id.read().await } - pub async fn is_identified(&self) -> bool { + + async fn is_identified(&self) -> bool { *self.identified.read().await && *self.challenged.read().await } - pub async fn handle_message(self: Arc, message: String) { - let cv = CommunicationValue::from_json(&message); - log_cv_in!(PrintType::Omikron, cv); + pub async fn handle_message(self: Arc, cv: CommunicationValue) { if cv.is_type(CommunicationType::ping) { self.handle_ping(cv).await; return; } + if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { let _ = task(self.clone(), cv.clone()); return; } - // ────────────────────────────── - // Identification - // ────────────────────────────── if !self.is_identified().await { - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; + self.handle_identification(cv).await; + return; + } - if !identified && cv.is_type(CommunicationType::identification) { - let omikron_id = cv - .get_data(DataTypes::omikron) - .and_then(|v| v.as_i64()) - .unwrap_or(0); + self.handle_authenticated(cv).await; + } - let (public_key, _) = match get_omikron_by_id(omikron_id).await { - Ok(v) => v, - Err(e) => { - self.send_message( + async fn handle_identification(self: &Arc, cv: CommunicationValue) { + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + if !identified && cv.is_type(CommunicationType::identification) { + let omikron_id = cv.get_data(DataTypes::omikron).as_number().unwrap_or(0); + + let (public_key, _) = match get_omikron_by_id(omikron_id).await { + Ok(v) => v, + Err(_) => { + let _ = self + .send( &CommunicationValue::new(CommunicationType::error_not_authenticated) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + .with_id(cv.get_id()), ) .await; - return; - } - }; + return; + } + }; - let pub_key_bytes = match STANDARD.decode(&public_key) { - Ok(b) => b, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_omikron_id, + let pub_key_bytes = match STANDARD.decode(&public_key) { + Ok(b) => b, + Err(_) => { + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error_invalid_omikron_id) + .with_id(cv.get_id()), ) .await; - return; - } - }; + return; + } + }; - let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { - Some(k) => k, - _ => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_public_key, + let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { + Some(k) => k, + _ => { + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error_invalid_public_key) + .with_id(cv.get_id()), ) .await; - return; - } - }; - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.omikron_id.write().await = omikron_id; - *self.challenge.write().await = challenge.clone(); - *self.pub_key.write().await = Some(pub_key_bytes); - *self.identified.write().await = true; - - let encrypted = - encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); - - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), - ) - .add_data_str(DataTypes::challenge, encrypted); - - self.send_message(&response).await; - return; - } - - if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { - let client_response = cv - .get_data(DataTypes::challenge) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - if client_response == *self.challenge.read().await { - *self.challenged.write().await = true; - omikron_manager::add_omikron(self.clone()).await; - self.send_message( - &CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, JsonValue::Boolean(true)), - ) - .await; - log_in!(PrintType::Omega, "Omikron Connected"); - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await; - self.close().await; + return; } - return; - } + }; - self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) - .await; - self.close().await; - } - let omikron_id = self.get_omikron_id().await; + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link) { - if let Some(link) = link.as_str() { - if let Ok(short_link) = add_short_link(link).await { - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, JsonValue::String(short_link)); - self.send_message(&response).await; - return; - } - } - } - } + *self.omikron_id.write().await = omikron_id; + *self.challenge.write().await = challenge.clone(); + *self.pub_key.write().await = Some(pub_key_bytes); + *self.identified.write().await = true; - // ONLINE STATUS TRACKING - if cv.is_type(CommunicationType::user_connected) { - log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status( - user_id, - UserStatus::user_online, - omikron_id, - ); - } - return; - } - if cv.is_type(CommunicationType::user_disconnected) { - log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - if let Some(status) = user_online_tracker::get_user_status(user_id) { - user_online_tracker::track_user_status( - user_id, - UserStatus::user_offline, - status.omikron_id, - ); - } - } - return; - } + let encrypted = + encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); - if cv.is_type(CommunicationType::iota_connected) { - log_in!(PrintType::Omega, "IOTA connected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - let mut user_ids = JsonValue::Array(Vec::new()); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - for user in users { - let _ = user_ids.push(JsonValue::from(user.0)); - user_online_tracker::track_user_status( - user.0, - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error, when loading users for IOTA"); - } - self.send_message( - &CommunicationValue::new(CommunicationType::iota_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, user_ids), - ) - .await; - } else { - log_in!(PrintType::General, "No IOTA ID found"); - } - return; - } - if cv.is_type(CommunicationType::iota_disconnected) { - log_in!(PrintType::Omega, "IOTA disconnected"); - - if let Some(v) = cv.get_data(DataTypes::iota_id) { - if let Some(iota_id) = v.as_i64() { - let iota_offline = - user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } - } - } - } - - return; - } - - if cv.is_type(CommunicationType::sync_client_iota_status) { - if let Some(json::JsonValue::Array(user_ids)) = - cv.get_data(DataTypes::user_ids).cloned() - { - for user_id_json in user_ids { - if let Some(user_id) = user_id_json.as_i64() { - user_online_tracker::track_user_status( - user_id, - UserStatus::user_online, - omikron_id, - ); - } - } - } - if let Some(json::JsonValue::Array(iota_ids)) = - cv.get_data(DataTypes::iota_ids).cloned() - { - for iota_id_json in iota_ids { - if let Some(iota_id) = iota_id_json.as_i64() { - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - } - } - } - return; - } - - // DATA RETURN - if cv.is_type(CommunicationType::get_user_data) { - if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { - if let Some(user_id) = user_id.as_i64() { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = get_by_user_id(user_id).await - { - let mut response = - CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username.clone()) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - - if let Some(display) = display { - if display.is_empty() { - response = response.add_data_str(DataTypes::display, username); - } else { - response = response.add_data_str(DataTypes::display, display); - } - } - if let Some(status) = status { - if !status.is_empty() { - response = response.add_data_str(DataTypes::status, status); - } - } - if let Some(about) = about { - if !about.is_empty() { - response = response.add_data_str(DataTypes::about, about); - } - } - if let Some(avatar) = avatar { - response = - response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); - } - - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .into_iter() - .map(|id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - - if let Some(user_status) = user_status { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(user_status.connection_type.to_string()), - ); - response = response.add_data( - DataTypes::omikron_id, - JsonValue::Number(Number::from(user_status.omikron_id)), - ); - } else { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(UserStatus::iota_offline.to_string()), - ); - } - - self.send_message(&response).await; - return; - } - } - } - if let Some(username) = cv.get_data(DataTypes::username).cloned() { - if let Some(username) = username.as_str() { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = get_by_username(username).await - { - let mut response = - CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username.clone()) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - - if let Some(display) = display { - if display.is_empty() { - response = response.add_data_str(DataTypes::display, username); - } else { - response = response.add_data_str(DataTypes::display, display); - } - } - if let Some(status) = status { - if !status.is_empty() { - response = response.add_data_str(DataTypes::status, status); - } - } - if let Some(about) = about { - if !about.is_empty() { - response = response.add_data_str(DataTypes::about, about); - } - } - if let Some(avatar) = avatar { - response = - response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); - } - - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - if let Some(user_status) = user_status { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(user_status.connection_type.to_string()), - ); - response = response.add_data( - DataTypes::omikron_id, - JsonValue::Number(Number::from(user_status.omikron_id)), - ); - } else { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(UserStatus::iota_offline.to_string()), - ); - } - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - - self.send_message(&response).await; - return; - } - } - } - - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send_message(&response).await; - - return; - } - if cv.is_type(CommunicationType::get_iota_data) { - if let Some(iota_id) = cv.get_data(DataTypes::iota_id) { - if let Some(iota_id) = iota_id.as_i64() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let mut response = - CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::iota_id, JsonValue::from(iota_id)); - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - self.send_message(&response).await; - return; - } - } - } - if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { - if let Some(user_id) = user_id.as_i64() { - if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(user_id).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let mut response = - CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(user_id)), - ) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ); - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - - self.send_message(&response).await; - return; - } - } - } - } - if let Some(username) = cv.get_data(DataTypes::username).cloned() { - if let Some(username) = username.as_str() { - if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_username(username).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let mut response = - CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(user_id)), - ) - .add_data_str(DataTypes::username, username.to_string()) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ); - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - self.send_message(&response).await; - return; - } - } - } - } - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send_message(&response).await; - - return; - } - - // REGISTERING - if cv.is_type(CommunicationType::get_register) { - let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::get_register) + let response = CommunicationValue::new(CommunicationType::challenge) .with_id(cv.get_id()) .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(register_id)), - ); - self.send_message(&response).await; + DataTypes::public_key, + DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + + let _ = self.send(&response).await; return; } - if cv.is_type(CommunicationType::complete_register_iota) { - let iota_id_opt = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()); - if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { - if let Some(iota_id) = iota_id_opt { - match sql::register_complete_iota(iota_id, public_key.to_string()).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; - } - } - } else { - // New logic to create iota and return id - match sql::create_new_iota(public_key.to_string()).await { - Ok(new_iota_id) => { - let response = - CommunicationValue::new(CommunicationType::complete_register_iota) - .with_id(cv.get_id()) - .add_data(DataTypes::iota_id, JsonValue::from(new_iota_id)); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; - } - } - } + if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { + let client_response = cv.get_data(DataTypes::challenge).as_str().unwrap_or(""); + + if client_response == *self.challenge.read().await { + *self.challenged.write().await = true; + + omikron_manager::add_omikron(self.clone()).await; + + let _ = self + .send( + &CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, DataValue::BoolTrue), + ) + .await; + + log_in!(PrintType::Omega, "Omikron Connected"); } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error_invalid_challenge) + .with_id(cv.get_id()), + ) .await; } - return; } - if cv.is_type(CommunicationType::complete_register_user) { - if let ( - Some(user_id), - Some(username), - Some(public_key), - Some(iota_id), - Some(reset_token), - ) = ( - cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::username).and_then(|v| v.as_str()), - cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), - cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), - ) { - match sql::register_complete_user( - user_id, - username.to_string(), - public_key.to_string(), - iota_id, - reset_token.to_string(), - ) - .await - { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) + } + + async fn handle_authenticated(self: &Arc, cv: CommunicationValue) { + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link).as_str() { + if let Ok(short) = add_short_link(link).await { + let _ = self + .send( + &CommunicationValue::new(CommunicationType::shorten_link) .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + .add_data(DataTypes::link, DataValue::Str(short)), ) .await; - } } - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; } return; } - // CHANGING DATA - if cv.is_type(CommunicationType::change_user_data) { - let user_id = cv.get_sender(); - let mut success = true; - let mut error_message = String::new(); - - if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { - if let Some(private_key_hash) = cv - .get_data(DataTypes::private_key_hash) - .and_then(|v| v.as_str()) - { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await - { - success = false; - error_message = e.to_string(); - } - } else { - success = false; - error_message = - "private_key_hash is required when changing public_key".to_string(); - } - } - - if success { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_message( - &CommunicationValue::new(CommunicationType::error) + if cv.is_type(CommunicationType::get_register) { + let register_id = sql::get_register_id().await; + let _ = self + .send( + &CommunicationValue::new(CommunicationType::get_register) .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, error_message), + .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)), ) .await; - } return; } - if cv.is_type(CommunicationType::change_iota_data) { - if let (user_id, Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_sender(), - cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), - cv.get_data(DataTypes::new_token).and_then(|v| v.as_str()), - ) { - match sql::get_by_user_id(user_id).await { - Ok(user) => { - let current_token = user.11; - if current_token == reset_token { - let mut success = true; - let mut error_message = String::new(); - if let Err(e) = sql::change_iota_id(user_id, iota_id).await { - success = false; - error_message = e.to_string(); - } - if success { - if let Err(e) = - sql::change_token(user_id, new_token.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - if success { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, error_message), - ) - .await; - } - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await; - } - } - Err(_) => { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) - .await; - } - } - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; - } - return; - } if cv.is_type(CommunicationType::delete_user) { let user_id = cv.get_sender(); - match sql::delete_user(user_id).await { + match sql::delete_user(user_id as i64).await { Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; + let _ = self + .send( + &CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()), + ) + .await; } Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), + ) + .await; } } return; } + if cv.is_type(CommunicationType::delete_iota) { - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - match sql::delete_iota(iota_id).await { + if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { + match sql::delete_iota(*iota_id).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success) .with_id(cv.get_id()); - self.send_message(&response).await; + self.send(&response).await; } Err(e) => { - self.send_message( + self.send( &CommunicationValue::new(CommunicationType::error) .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), ) .await; } } } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) .await; } return; @@ -885,85 +324,68 @@ impl OmikronConnection { // NOTIFICATIONS if cv.is_type(CommunicationType::get_notifications) { let user_id = cv.get_sender(); - if let Ok(notifications) = sql::get_notifications(user_id).await { + if let Ok(notifications) = sql::get_notifications(user_id as i64).await { let mut json_array = Vec::new(); for (sender, amount) in notifications { - let mut obj = JsonValue::new_object(); - let _ = obj.insert("sender", JsonValue::from(sender)); - let _ = obj.insert("amount", JsonValue::from(amount)); - json_array.push(obj); + let mut obj = Vec::new(); + let _ = obj.push((DataTypes::sender_id, DataValue::Number(sender))); + let _ = obj.push((DataTypes::amount, DataValue::Number(amount))); + json_array.push(DataValue::Container(obj)); } let response = CommunicationValue::new(CommunicationType::get_notifications) .with_id(cv.get_id()) - .add_array(DataTypes::notifications, json_array); - self.send_message(&response).await; + .add_data(DataTypes::notifications, DataValue::Array(json_array)); + self.send(&response).await; } } if cv.is_type(CommunicationType::read_notification) { if let (user_id, Some(other_id)) = ( cv.get_sender(), - cv.get_data(DataTypes::sender_id) - .unwrap_or(&JsonValue::Null) - .as_i64(), + cv.get_data(DataTypes::sender_id).as_number(), ) { - if let Ok(_) = sql::read_notification(user_id, other_id).await { + if let Ok(_) = sql::read_notification(user_id as i64, other_id).await { let response = CommunicationValue::new(CommunicationType::read_notification) .with_id(cv.get_id()); - self.send_message(&response).await; + self.send(&response).await; } } } if cv.is_type(CommunicationType::push_notification) { if let (user_id, Some(other_id)) = ( cv.get_sender(), - cv.get_data(DataTypes::sender_id) - .unwrap_or(&JsonValue::Null) - .as_i64(), + cv.get_data(DataTypes::sender_id).as_number(), ) { - if let Ok(_) = sql::add_notification(user_id, other_id).await { + if let Ok(_) = sql::add_notification(user_id as i64, other_id).await { let response = CommunicationValue::new(CommunicationType::push_notification) .with_id(cv.get_id()); - self.send_message(&response).await; + self.send(&response).await; } } } } - 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; - } - pub async fn close(&self) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - } - pub async fn handle_close(self: Arc) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - } - async fn handle_ping(&self, cv: CommunicationValue) { - 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; + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + if let Ok(val) = last_ping.to_string().parse::() { + *self.ping.write().await = val; } } - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + let _ = self + .send(&CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id())) + .await; + } - self.send_message(&response).await; + pub async fn handle_close(&self) { + if self.is_identified().await { + let omikron_id = self.get_omikron_id().await; + + if omikron_id != 0 { + log_in!(PrintType::Omega, "Omikron Disconnected"); + + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; + } + } } } diff --git a/src/server/server.rs b/src/server/server.rs index 5167444..c1b0e10 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,11 +1,10 @@ use crate::{ log, - server::{api, short_link::get_short_link, socket}, + server::{api, short_link::get_short_link}, util::file_util::get_directory, }; use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; -use actix_web_actors::ws; use rustls::ServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; @@ -42,7 +41,6 @@ pub async fn start(port: u16) -> anyhow::Result<()> { App::new() .route("/api/{path:.*}", web::to(api_handler)) .route("/direct/{path:.*}", web::to(direct_handler)) - .route("/ws/{path:.*}", web::get().to(ws_handler)) }) .bind_rustls_0_23(addr, config)? .run() @@ -64,16 +62,6 @@ async fn direct_handler(req: HttpRequest) -> impl Responder { .finish() } } -async fn ws_handler( - req: HttpRequest, - stream: web::Payload, - path: web::Path, -) -> Result { - let path = path.into_inner(); - println!("WS handler reached: {}", path); - - ws::start(socket::WsSession::new(path), &req, stream) -} async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { let path = req.uri().path().to_string(); diff --git a/src/server/socket.rs b/src/server/socket.rs deleted file mode 100755 index 7d04b26..0000000 --- a/src/server/socket.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::sync::Arc; -use std::time::{Duration, Instant}; - -use actix::{Actor, ActorContext, AsyncContext, StreamHandler}; -use actix_web_actors::ws; - -use crate::data::communication::{CommunicationType, CommunicationValue}; -use crate::log; -use crate::server::omikron_connection::OmikronConnection; - -const IDLE_TIMEOUT: Duration = Duration::from_secs(30); - -use actix::Message; - -#[derive(Message)] -#[rtype(result = "()")] -pub struct WsSendMessage(pub String); - -impl actix::Handler for WsSession { - type Result = (); - - fn handle(&mut self, msg: WsSendMessage, ctx: &mut Self::Context) { - ctx.text(msg.0); - } -} - -pub struct WsSession { - path: String, - last_heartbeat: Instant, - omikron: Option>, -} - -impl WsSession { - pub fn new(path: String) -> Self { - Self { - path, - last_heartbeat: Instant::now(), - omikron: None, - } - } - - fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext) { - ctx.run_interval(Duration::from_secs(5), |act, ctx| { - if Instant::now().duration_since(act.last_heartbeat) > IDLE_TIMEOUT { - log!("[ws_handler] Heartbeat failed. Disconnecting."); - ctx.close(None); - ctx.stop(); - return; - } - - let ping = CommunicationValue::new(CommunicationType::ping) - .to_json() - .to_string(); - - ctx.text(ping); - }); - } -} - -impl Actor for WsSession { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.start_heartbeat(ctx); - - if self.path == "omikron" { - let addr = ctx.address(); - let connection = OmikronConnection::new(addr); - self.omikron = Some(connection); - } - } - - fn stopped(&mut self, _: &mut Self::Context) { - log!( - "[ws] WebSocket handling task for path: {} is finished.", - self.path - ); - - if let Some(conn) = &self.omikron { - let conn = conn.clone(); - actix_rt::spawn(async move { - conn.handle_close().await; - }); - } - } -} -impl StreamHandler> for WsSession { - fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg { - Ok(ws::Message::Text(text)) => { - self.last_heartbeat = Instant::now(); - - if let Some(conn) = &self.omikron { - let conn_clone = conn.clone(); - actix_rt::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - } - - Ok(ws::Message::Ping(msg)) => { - self.last_heartbeat = Instant::now(); - ctx.pong(&msg); - } - - Ok(ws::Message::Pong(_)) => { - self.last_heartbeat = Instant::now(); - log!("[ws_handler] Received Pong. Connection alive."); - } - - Ok(ws::Message::Close(reason)) => { - log!("[ws_handler] Received Close. Disconnecting."); - ctx.close(reason); - ctx.stop(); - } - - Ok(ws::Message::Binary(_)) => { - log!("[ws_handler] Binary message ignored."); - } - - Err(e) => { - log!("[ERROR] WS Error: {}. Closing.", e); - ctx.stop(); - } - - _ => {} - } - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index 80bc7c6..e861e69 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -8,10 +9,9 @@ use std::{ }; use ansi_term::Color; +use epsilon_core::{CommunicationValue, DataTypes, DataValue}; use json::JsonValue; -use crate::data::communication::CommunicationValue; - static LOGGER: OnceLock> = OnceLock::new(); #[derive(Clone, Copy)] @@ -224,7 +224,7 @@ pub fn log_cv_internal( let formatted = format_cv(cv); log_internal( - Some(cv.get_sender()), + Some(cv.get_sender() as i64), print_type.unwrap_or(PrintType::General), prefix, false, @@ -249,24 +249,79 @@ pub fn format_cv(cv: &CommunicationValue) -> String { let comm_type = cv.get_type().to_string(); parts.push(format!("{}", comm_type)); - let mut data_parts = Vec::new(); - if let JsonValue::Object(data) = &cv.clone().to_json()["data"] { - for (key, value) in data.iter() { - let val_string = match value { - JsonValue::String(s) => s.clone(), - _ => value.dump(), - }; + let data: &HashMap = cv.get_data_container(); - data_parts.push(format!("{} {}", key, val_string)); - } - } + let formated_data = + format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); - if !data_parts.is_empty() { - parts.push(format!("{}", data_parts.join(", "))); - } + parts.push(format!("{}", formated_data)); parts.join(": ") } + +fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { + let parts: Vec = data + .into_iter() + .map(|(key, value)| { + let key_str = key.to_string(); + + match value { + DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{}={{ {} }}", key_str, inner_formatted) + } + + DataValue::Array(arr) => { + let arr_formatted = format_array(arr); + format!("{}=[{}]", key_str, arr_formatted) + } + + DataValue::Bool(b) => format!("{}={}", key_str, b), + + DataValue::BoolTrue => format!("{}=true", key_str), + DataValue::BoolFalse => format!("{}=false", key_str), + + DataValue::Number(num) => format!("{}={}", key_str, num), + + _ => "".to_string(), + } + }) + .collect(); + + parts.join(", ") +} + +fn format_array(arr: Vec) -> String { + let parts: Vec = arr + .into_iter() + .map(|value| match value { + DataValue::Str(s) => format!("\"{}\"", s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{{ {} }}", inner_formatted) + } + + DataValue::Array(inner_arr) => { + let formatted = format_array(inner_arr); + format!("[{}]", formatted) + } + + DataValue::Bool(b) => b.to_string(), + + DataValue::BoolTrue => "true".to_string(), + DataValue::BoolFalse => "false".to_string(), + + DataValue::Number(num) => num.to_string(), + + _ => String::new(), + }) + .collect(); + + parts.join(", ") +} #[macro_export] macro_rules! log_cv { ($kind:expr, $cv:expr) => { From ac8c4512fdd6ff15c0b6b41f6a8434fb18200c58 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 27 Feb 2026 23:45:22 +0100 Subject: [PATCH 078/220] [FIX] Migrated to Tensamin Transport Protocol --- Cargo.lock | 267 +++++--- Cargo.toml | 8 +- src/data/communication.rs | 403 ----------- src/data/mod.rs | 1 - src/main.rs | 6 +- src/server/api.rs | 234 ++++--- src/server/mod.rs | 1 - src/server/omikron_connection.rs | 1068 +++++++----------------------- src/server/server.rs | 14 +- src/server/socket.rs | 129 ---- src/util/logger.rs | 87 ++- 11 files changed, 626 insertions(+), 1592 deletions(-) delete mode 100644 src/data/communication.rs delete mode 100644 src/data/mod.rs delete mode 100755 src/server/socket.rs diff --git a/Cargo.lock b/Cargo.lock index 7469a5e..40c314f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,6 +21,8 @@ dependencies = [ "color-eyre", "dashmap", "dotenv", + "epsilon-core", + "epsilon-native", "futures", "futures-util", "hex", @@ -32,16 +34,17 @@ dependencies = [ "json", "once_cell", "pnet", + "quinn", "rand 0.8.5", "rand_core 0.6.4", "reqwest", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pemfile 2.2.0", "sha1", "sha2", "sqlx", - "strum", - "strum_macros", + "strum 0.27.2", + "strum_macros 0.27.2", "sysinfo", "tokio", "tokio-rustls", @@ -99,9 +102,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.11.2" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7926860314cbe2fb5d1f13731e387ab43bd32bca224e82e6e2db85de0a3dba49" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" dependencies = [ "actix-codec", "actix-rt", @@ -149,9 +152,9 @@ dependencies = [ [[package]] name = "actix-router" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" dependencies = [ "bytestring", "cfg-if", @@ -231,9 +234,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.12.1" +version = "4.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1654a77ba142e37f049637a3e5685f864514af11fcbc51cb51eb6596afe5b8d6" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" dependencies = [ "actix-codec", "actix-http", @@ -405,9 +408,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.101" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arbitrary" @@ -443,9 +446,9 @@ dependencies = [ [[package]] name = "async-executor" -version = "1.13.3" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497c00e0fd83a72a79a39fcbd8e3e2f055d6f6c7e025f3b3d91f4f8e76527fb8" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", @@ -503,7 +506,7 @@ dependencies = [ "futures-lite 2.6.1", "parking", "polling 3.11.0", - "rustix 1.1.3", + "rustix 1.1.4", "slab", "windows-sys 0.61.2", ] @@ -649,9 +652,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.4" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -817,9 +820,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" @@ -1113,9 +1116,9 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deflate64" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26bf8fc351c5ed29b5c2f0cbbac1b209b74f60ecd62e675a998df72c49af5204" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" [[package]] name = "der" @@ -1130,9 +1133,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", ] @@ -1241,6 +1244,32 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "epsilon-core" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +dependencies = [ + "byteorder", + "rand 0.8.5", + "strum 0.28.0", + "strum_macros 0.28.0", +] + +[[package]] +name = "epsilon-native" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "epsilon-core", + "quinn", + "rustls 0.23.37", + "thiserror 2.0.18", + "tokio", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -1305,6 +1334,18 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fastbloom" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" +dependencies = [ + "getrandom 0.3.4", + "libm", + "rand 0.9.2", + "siphasher", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1398,9 +1439,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1413,9 +1454,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1423,15 +1464,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1451,9 +1492,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-lite" @@ -1485,9 +1526,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", @@ -1496,21 +1537,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1520,7 +1561,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1903,7 +1943,7 @@ dependencies = [ "hyper", "hyper-util", "log", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-pki-types", "tokio", @@ -2163,9 +2203,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -2233,7 +2273,7 @@ checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags 2.11.0", "libc", - "redox_syscall 0.7.1", + "redox_syscall 0.7.3", ] [[package]] @@ -2254,9 +2294,9 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" @@ -2367,9 +2407,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.16" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5d26952a508f321b4d3d2e80e78fc2603eaefcdf0c30783867f19586518bdc" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -2535,9 +2575,9 @@ dependencies = [ [[package]] name = "owo-colors" -version = "4.2.3" +version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" [[package]] name = "parking" @@ -2595,9 +2635,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "pin-utils" @@ -2760,7 +2800,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.5.2", "pin-project-lite", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -2846,7 +2886,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.36", + "rustls 0.23.37", "socket2 0.6.2", "thiserror 2.0.18", "tokio", @@ -2862,13 +2902,15 @@ checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "aws-lc-rs", "bytes", + "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pki-types", + "rustls-platform-verifier", "slab", "thiserror 2.0.18", "tinyvec", @@ -2981,9 +3023,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ "bitflags 2.11.0", ] @@ -3019,9 +3061,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" @@ -3046,7 +3088,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pki-types", "rustls-platform-verifier", "sync_wrapper", @@ -3132,14 +3174,14 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] @@ -3157,13 +3199,14 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.36" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "aws-lc-rs", "log", "once_cell", + "ring", "rustls-pki-types", "rustls-webpki 0.103.9", "subtle", @@ -3221,7 +3264,7 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-native-certs", "rustls-platform-verifier-android", "rustls-webpki 0.103.9", @@ -3307,9 +3350,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d17b898a6d6948c3a8ee4372c17cb384f90d2e6e912ef00895b14fd7ab54ec38" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags 2.11.0", "core-foundation 0.10.1", @@ -3320,9 +3363,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.16.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "321c8673b092a9a42605034a9879d73cb79101ed5fd117bc9a597b89b4e9e61a" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3472,6 +3515,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "slab" version = "0.4.12" @@ -3747,6 +3796,12 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + [[package]] name = "strum_macros" version = "0.27.2" @@ -3759,6 +3814,18 @@ dependencies = [ "syn", ] +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "subtle" version = "2.6.1" @@ -3767,9 +3834,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.115" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -3852,14 +3919,14 @@ checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" [[package]] name = "tempfile" -version = "3.25.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand 2.3.0", "getrandom 0.4.1", "once_cell", - "rustix 1.1.3", + "rustix 1.1.4", "windows-sys 0.61.2", ] @@ -4023,7 +4090,7 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.36", + "rustls 0.23.37", "tokio", ] @@ -4094,9 +4161,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.8+spec-1.1.0" +version = "1.0.9+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0742ff5ff03ea7e67c8ae6c93cac239e0d9784833362da3f9a9c1da8dfefcbdc" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" dependencies = [ "winnow", ] @@ -4226,7 +4293,7 @@ dependencies = [ "log", "native-tls", "rand 0.9.2", - "rustls 0.23.36", + "rustls 0.23.37", "rustls-pki-types", "sha1", "thiserror 2.0.18", @@ -4248,9 +4315,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -4417,9 +4484,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -4430,9 +4497,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.58" +version = "0.4.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" dependencies = [ "cfg-if", "futures-util", @@ -4444,9 +4511,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4454,9 +4521,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -4467,9 +4534,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] @@ -4510,9 +4577,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -5179,18 +5246,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", @@ -5300,9 +5367,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.0" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" +checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 27766b7..8432d26 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] +epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } +epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } + actix = "0.13.5" actix-rt = "2.11.0" actix-web = { version = "4.12.1", features = ["rustls-0_23"] } @@ -44,7 +47,7 @@ async-tungstenite = { version = "0.32.0", features = [ "verbose-logging", "webpki-roots", ] } -axum = { version = "0.8.8", features = [ "ws", "http2" ] } +axum = { version = "0.8.8", features = ["ws", "http2"] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" @@ -64,7 +67,7 @@ pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } reqwest = "0.13.2" -rustls = "0.23.36" +rustls = "0.23.37" rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" @@ -82,3 +85,4 @@ uuid = { version = "1.19.0", features = ["v4"] } walkdir = "2.5.0" x448 = "0.6.0" zip = "6.0.0" +quinn = "0.11.9" diff --git a/src/data/communication.rs b/src/data/communication.rs deleted file mode 100644 index 99229f5..0000000 --- a/src/data/communication.rs +++ /dev/null @@ -1,403 +0,0 @@ -use json::number::Number; -use json::{Array, JsonValue, object, parse}; -use std::collections::HashMap; -use std::time::{SystemTime, UNIX_EPOCH}; -use strum::IntoEnumIterator; -use strum_macros::EnumIter; -use uuid::Uuid; - -#[derive(Eq, Hash, PartialEq, EnumIter, Clone, Debug)] -#[allow(non_camel_case_types, dead_code)] -pub enum DataTypes { - error_type, - accepted_ids, - uuid, - register_id, - - link, - - settings, - settings_name, - chat_partner_id, - chat_partner_name, - iota_id, - user_id, - user_ids, - iota_ids, - user_state, - user_states, - user_pings, - call_state, - screen_share, - private_key_hash, - accepted, - accepted_profiles, - denied_profiles, - content, - messages, - notifications, - send_time, - get_time, - get_variant, - shared_secret_own, - shared_secret_other, - shared_secret_sign, - shared_secret, - call_id, - call_token, - untill, - enabled, - start_date, - end_date, - receiver_id, - sender_id, - signature, - signed, - message, - message_state, - 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, - rho_connections, - user, - online_status, - omikron_id, - omikron_connections, - reset_token, - new_token, -} - -impl DataTypes { - pub fn parse(p0: String) -> DataTypes { - for datatype in DataTypes::iter() { - if datatype.to_string().to_lowercase().replace('_', "") - == p0.to_lowercase().replace('_', "") - { - return datatype; - } - } - DataTypes::error_type - } - pub fn to_string(&self) -> String { - return format!("{:?}", self); - } -} - -#[derive(PartialEq, Clone, EnumIter, Debug)] -#[allow(non_camel_case_types, dead_code)] -pub enum CommunicationType { - error, - error_anonymous, - error_internal, - error_invalid_data, - error_invalid_user_id, - error_invalid_omikron_id, - error_not_found, - error_not_authenticated, - error_no_iota, - error_invalid_challenge, - error_invalid_secret, - error_invalid_private_key, - error_invalid_public_key, - error_no_user_id, - error_no_call_id, - error_invalid_call_id, - success, - - shorten_link, - - settings_save, - settings_load, - settings_list, - message, - message_state, - message_send, - message_live, - message_other_iota, - message_chunk, - messages_get, - - push_notification, - read_notification, - get_notifications, - - 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, - register_iota, - register_iota_success, - ping, - pong, - add_conversation, - send_chat, - client_changed, - client_connected, - client_disconnected, - client_closed, - public_key, - private_key, - webrtc_sdp, - webrtc_ice, - start_stream, - end_stream, - watch_stream, - call_token, - call_invite, - call_disconnect_user, - call_timeout_user, - call_set_anonymous_joining, - end_call, - function, - update, - create_user, - rho_update, - - user_connected, - user_disconnected, - iota_connected, - iota_disconnected, - sync_client_iota_status, - - get_user_data, - get_iota_data, - iota_user_data, - - change_user_data, - change_iota_data, - - get_register, - complete_register_user, - complete_register_iota, - delete_user, - delete_iota, - - start_register, - complete_register, -} -impl CommunicationType { - pub fn parse(p0: String) -> CommunicationType { - for datatype in CommunicationType::iter() { - if datatype.to_string().to_lowercase().replace('_', "") - == p0.to_lowercase().replace('_', "") - { - return datatype; - } - } - CommunicationType::error - } - pub fn to_string(&self) -> String { - return format!("{:?}", self); - } -} - -#[derive(Debug, Clone)] -pub struct CommunicationValue { - id: Uuid, - comm_type: CommunicationType, - sender: i64, - receiver: i64, - data: HashMap, -} - -#[allow(dead_code)] -impl CommunicationValue { - pub fn new(comm_type: CommunicationType) -> Self { - Self { - id: Uuid::new_v4(), - comm_type, - sender: 0, - receiver: 0, - 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: i64) -> Self { - self.sender = sender; - self - } - pub fn get_sender(&self) -> i64 { - self.sender.clone() - } - pub fn with_receiver(mut self, receiver: i64) -> Self { - self.receiver = receiver; - self - } - pub fn get_receiver(&self) -> i64 { - 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 fn get_type(&self) -> CommunicationType { - self.comm_type.clone() - } - pub 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()); - } - if self.sender > 0 && self.receiver > 0 { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - sender: self.sender, - receiver: self.receiver, - data: jdata - } - } else if self.sender > 0 { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - sender: self.sender, - data: jdata - } - } else if self.receiver > 0 { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - receiver: self.receiver, - data: jdata - } - } else { - object! { - id: self.id.to_string(), - type: format!("{:?}", self.comm_type), - data: jdata - } - } - } - - pub fn from_json(json_str: &str) -> Self { - if let Ok(parsed) = parse(json_str) { - let comm_type = CommunicationType::parse(parsed["type"].to_string()); - let mut sender: i64 = 0; - if parsed.has_key("sender") { - sender = parsed["sender"].as_i64().unwrap_or(0); - } - let mut receiver: i64 = 0; - if parsed.has_key("receiver") { - receiver = parsed["receiver"].as_i64().unwrap_or(0); - } - - 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, - } - } else { - Self { - id: Uuid::new_v4(), - comm_type: CommunicationType::error, - sender: 0, - receiver: 0, - data: HashMap::new(), - } - } - } - pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue { - let receiver = original - .get_data(DataTypes::receiver_id) - .unwrap_or(&JsonValue::Number(Number::from(0))) - .as_i64() - .unwrap_or(0); - - let now_ms = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as i64; - - let sender = original.get_sender(); - CommunicationValue::new(CommunicationType::message_other_iota) - .with_id(original.get_id()) - .with_receiver(receiver) - .add_data( - DataTypes::receiver_id, - JsonValue::Number(Number::from(receiver)), - ) - .with_sender(sender) - .add_data(DataTypes::send_time, JsonValue::String(now_ms.to_string())) - .add_data( - DataTypes::sender_id, - JsonValue::Number(Number::from(sender)), - ) - .add_data( - DataTypes::content, - JsonValue::String(original.get_data(DataTypes::content).unwrap().to_string()), - ) - } -} diff --git a/src/data/mod.rs b/src/data/mod.rs deleted file mode 100644 index 73c2657..0000000 --- a/src/data/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod communication; diff --git a/src/main.rs b/src/main.rs index 88637e6..043700c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ -mod data; mod server; mod sql; mod util; +use crate::server::omikron_connection; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::util::crypto_helper::load_public_key; @@ -43,7 +43,7 @@ async fn main() { } else { log!(" Users"); } - let _ = server::server::start(9187).await; - + let _ = server::server::start(9188).await; + let _ = omikron_connection::OmikronServer::start(9187).await; tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/api.rs b/src/server/api.rs index f826e3e..7f3c728 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,4 +1,3 @@ -use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::get_public_key; use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; @@ -11,7 +10,6 @@ use actix_web::HttpResponse; use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; -use json::number::Number; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { if path == "OPTIONS" { @@ -35,6 +33,9 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { }; let (status, body_text) = match path_parts.as_slice() { + // ================================================== + // DOWNLOAD IOTA FRONTEND + // ================================================== ["api", "download", "iota_frontend"] => { let file_path = "downloads/[iota_frontend].zip"; @@ -50,87 +51,107 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { .body(file_bytes); } Err(_) => { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); return HttpResponse::NotFound() .insert_header(("Access-Control-Allow-Origin", "*")) - .body("File not found"); + .body(res.dump()); } } } + + // ================================================== + // GET RANDOM OMIKRON + // ================================================== ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { - if let Ok((public_key, ip_address)) = - sql::get_omikron_by_id(omikron_conn.get_omikron_id().await).await - { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_conn.get_omikron_id().await, - public_key, - ip_address - ), - ) + let id = omikron_conn.get_omikron_id().await; + + if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + + (StatusCode::OK, res.dump()) } else { - ( - StatusCode::INTERNAL_SERVER_ERROR, - "selected an invalid omikron".to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) } } else { - ( - StatusCode::NOT_FOUND, - "couldn't find online omikron".to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } + + // ================================================== + // GET OMIKRON BY ID + // ================================================== ["api", "get", "omikron", id] => { let id = id.parse::().unwrap_or(0); + if id == 0 { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - id, public_key, ip_address - ), - ) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await { if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - ( - StatusCode::OK, - format!( - "{{\"id\": {}, \"public_key\": \"{}\", \"ip_address\": \"{}\"}}", - omikron_id, public_key, ip_address - ), - ) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } else { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) } } + + // ================================================== + // GET ID BY USERNAME + // ================================================== ["api", "get", "id", username] => { if username.is_empty() { - not_found() + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) } else if let Ok(( id, iota_id, @@ -146,37 +167,49 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { _, )) = sql::get_by_username(username).await { - let cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); - (StatusCode::OK, cv.to_json().to_string()) + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + (StatusCode::OK, res.dump()) } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) } } - ["api", "get", "public_key"] => (StatusCode::OK, public_key_to_base64(&get_public_key())), + + // ================================================== + // GET SERVER PUBLIC KEY + // ================================================== + ["api", "get", "public_key"] => { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["public_key"] = public_key_to_base64(&get_public_key()).into(); + (StatusCode::OK, res.dump()) + } + + // ================================================== + // GET USER BY ID + // ================================================== ["api", "get", "user", id] => { let id: i64 = id.parse().unwrap_or(0); + if id == 0 { - bad_request() + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) } else if let Ok(( id, iota_id, username, display, - status, + status_msg, about, avatar, sub_level, @@ -186,47 +219,46 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { _, )) = sql::get_by_user_id(id).await { - let mut cv = CommunicationValue::new(CommunicationType::success) - .add_data_str(DataTypes::username, username) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data(DataTypes::iota_id, JsonValue::Number(Number::from(iota_id))) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data(DataTypes::sub_end, JsonValue::Number(Number::from(sub_end))); + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + if let Some(display) = display { - cv = cv.add_data_str(DataTypes::display, display); + res["display"] = display.into(); } - if let Some(status) = status { - cv = cv.add_data_str(DataTypes::status, status); + if let Some(status_msg) = status_msg { + res["status_message"] = status_msg.into(); } if let Some(about) = about { - cv = cv.add_data_str(DataTypes::about, about); + res["about"] = about.into(); } if let Some(avatar) = avatar { - cv = cv.add_data_str( - DataTypes::avatar, - base64::engine::general_purpose::STANDARD.encode(avatar), - ); + res["avatar"] = base64::engine::general_purpose::STANDARD + .encode(avatar) + .into(); } - (StatusCode::OK, cv.to_json().to_string()) + + (StatusCode::OK, res.dump()) } else { - ( - StatusCode::OK, - CommunicationValue::new(CommunicationType::error_not_found) - .to_json() - .to_string(), - ) + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) } } - _ => ( - StatusCode::INTERNAL_SERVER_ERROR, - CommunicationValue::new(CommunicationType::error) - .to_json() - .to_string(), - ), + + // ================================================== + // DEFAULT + // ================================================== + _ => { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } }; let body_bytes = body_text.into_bytes(); diff --git a/src/server/mod.rs b/src/server/mod.rs index 72ff9f4..aab6ab6 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -3,4 +3,3 @@ pub mod omikron_connection; pub mod omikron_manager; pub mod server; pub mod short_link; -pub mod socket; diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 08a2ead..0ae49b7 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,43 +1,98 @@ -use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; -use crate::server::omikron_manager; -use crate::server::short_link::add_short_link; -use crate::server::socket::{WsSendMessage, WsSession}; -use crate::sql::connection_status::UserStatus; -use crate::sql::sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}; -use crate::sql::user_online_tracker::{self}; -use crate::util::crypto_helper::encrypt; -use crate::util::logger::PrintType; -use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in, log_out}; -use actix::Addr; +use crate::server::{omikron_manager, short_link::add_short_link}; +use crate::sql::{sql, sql::get_omikron_by_id, user_online_tracker}; +use crate::util::file_util::load_file_buf; +use crate::util::{crypto_helper::encrypt, logger::PrintType}; +use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use json::JsonValue; -use json::number::Number; -use rand::Rng; -use rand::distributions::Alphanumeric; +use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use epsilon_native::{Receiver, Sender, host}; +use quinn::ServerConfig; +use rand::{Rng, distributions::Alphanumeric}; +use rustls::pki_types::PrivateKeyDer; use std::sync::Arc; use tokio::sync::RwLock; -use uuid::Uuid; use x448::PublicKey; +pub struct OmikronServer; + +impl OmikronServer { + pub async fn start(port: u16) -> Result<(), Box> { + let tls_cfg = OmikronServer::load_tls().expect("TLS config failed"); + let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg) + .expect("Failed to convert to QuicServerConfig"); + let mut host = host(port, ServerConfig::with_crypto(Arc::new(server_crypto))).await?; + tokio::spawn(async move { + while let Some((sender, receiver)) = host.next().await { + let connection = OmikronConnection::new(sender); + tokio::spawn(Self::connection_loop(connection, receiver)); + } + }); + + Ok(()) + } + fn load_tls() -> Option { + let mut cert_file_buf = load_file_buf("certs", "cert.pem").ok()?; + let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; + + let cert_chain = rustls_pemfile::certs(&mut cert_file_buf) + .collect::, _>>() + .ok()?; + + let mut keys: Vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_buf) + .map(|k| k.map(Into::into)) + .collect::, _>>() + .ok()?; + + if keys.is_empty() { + let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; + keys = rustls_pemfile::rsa_private_keys(&mut key_file_buf) + .map(|k| k.map(Into::into)) + .collect::, _>>() + .ok()?; + } + + if keys.is_empty() { + return None; + } + + let cfg = rustls::ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, keys.remove(0)) + .ok()?; + + Some(cfg) + } + + async fn connection_loop(conn: Arc, receiver: Receiver) { + while let Ok(cv) = receiver.receive().await { + log_cv_in!(PrintType::Omikron, cv); + conn.clone().handle_message(cv).await; + } + + conn.handle_close().await; + } +} + pub struct OmikronConnection { - pub ws_addr: Arc>>, - pub omikron_id: Arc>, - pub pub_key: Arc>>>, + sender: Arc>>, + omikron_id: Arc>, + pub_key: Arc>>>, + identified: Arc>, challenged: Arc>, challenge: Arc>, - pub ping: Arc>, - waiting_tasks: DashMap< - Uuid, - Box, CommunicationValue) -> bool + Send + Sync>, - >, + + ping: Arc>, + + waiting_tasks: + DashMap, CommunicationValue) -> bool + Send + Sync>>, } impl OmikronConnection { - pub fn new(ws_addr: Addr) -> Arc { + pub fn new(sender: Sender) -> Arc { Arc::new(Self { - ws_addr: Arc::new(RwLock::new(ws_addr)), + sender: Arc::new(RwLock::new(Some(sender))), omikron_id: Arc::new(RwLock::new(0)), pub_key: Arc::new(RwLock::new(None)), identified: Arc::new(RwLock::new(false)), @@ -47,836 +102,220 @@ impl OmikronConnection { waiting_tasks: DashMap::new(), }) } - pub async fn send_message(&self, cv: &CommunicationValue) { - let text = cv.to_json().to_string(); + async fn send(&self, cv: &CommunicationValue) { log_cv_out!(PrintType::Omikron, cv); - self.ws_addr.read().await.do_send(WsSendMessage(text)); + if let Some(sender) = self.sender.read().await.as_ref() { + if sender.send(cv).await.is_err() { + self.handle_close().await; + } + } + } + + async fn send_error_response(&self, id: u32, comm_type: CommunicationType) { + let response = CommunicationValue::new(comm_type).with_id(id); + self.send(&response).await; } pub async fn get_omikron_id(&self) -> i64 { - let omikron_id = { - let guard = self.omikron_id.read().await; - guard.clone() - }; - omikron_id + *self.omikron_id.read().await } - pub async fn is_identified(&self) -> bool { + + async fn is_identified(&self) -> bool { *self.identified.read().await && *self.challenged.read().await } - pub async fn handle_message(self: Arc, message: String) { - let cv = CommunicationValue::from_json(&message); - log_cv_in!(PrintType::Omikron, cv); + pub async fn handle_message(self: Arc, cv: CommunicationValue) { if cv.is_type(CommunicationType::ping) { self.handle_ping(cv).await; return; } + if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { let _ = task(self.clone(), cv.clone()); return; } - // ────────────────────────────── - // Identification - // ────────────────────────────── if !self.is_identified().await { - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; + self.handle_identification(cv).await; + return; + } - if !identified && cv.is_type(CommunicationType::identification) { - let omikron_id = cv - .get_data(DataTypes::omikron) - .and_then(|v| v.as_i64()) - .unwrap_or(0); + self.handle_authenticated(cv).await; + } - let (public_key, _) = match get_omikron_by_id(omikron_id).await { - Ok(v) => v, - Err(e) => { - self.send_message( + async fn handle_identification(self: &Arc, cv: CommunicationValue) { + let identified = *self.identified.read().await; + let challenged = *self.challenged.read().await; + + if !identified && cv.is_type(CommunicationType::identification) { + let omikron_id = cv.get_data(DataTypes::omikron).as_number().unwrap_or(0); + + let (public_key, _) = match get_omikron_by_id(omikron_id).await { + Ok(v) => v, + Err(_) => { + let _ = self + .send( &CommunicationValue::new(CommunicationType::error_not_authenticated) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + .with_id(cv.get_id()), ) .await; - return; - } - }; + return; + } + }; - let pub_key_bytes = match STANDARD.decode(&public_key) { - Ok(b) => b, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_omikron_id, + let pub_key_bytes = match STANDARD.decode(&public_key) { + Ok(b) => b, + Err(_) => { + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error_invalid_omikron_id) + .with_id(cv.get_id()), ) .await; - return; - } - }; + return; + } + }; - let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { - Some(k) => k, - _ => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_public_key, + let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { + Some(k) => k, + _ => { + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error_invalid_public_key) + .with_id(cv.get_id()), ) .await; - return; - } - }; - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.omikron_id.write().await = omikron_id; - *self.challenge.write().await = challenge.clone(); - *self.pub_key.write().await = Some(pub_key_bytes); - *self.identified.write().await = true; - - let encrypted = - encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); - - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data_str( - DataTypes::public_key, - STANDARD.encode(get_public_key().as_bytes()), - ) - .add_data_str(DataTypes::challenge, encrypted); - - self.send_message(&response).await; - return; - } - - if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { - let client_response = cv - .get_data(DataTypes::challenge) - .and_then(|v| v.as_str()) - .unwrap_or(""); - - if client_response == *self.challenge.read().await { - *self.challenged.write().await = true; - omikron_manager::add_omikron(self.clone()).await; - self.send_message( - &CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, JsonValue::Boolean(true)), - ) - .await; - log_in!(PrintType::Omega, "Omikron Connected"); - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await; - self.close().await; + return; } - return; - } + }; - self.send_error_response(&cv.get_id(), CommunicationType::error_not_authenticated) - .await; - self.close().await; - } - let omikron_id = self.get_omikron_id().await; + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link) { - if let Some(link) = link.as_str() { - if let Ok(short_link) = add_short_link(link).await { - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, JsonValue::String(short_link)); - self.send_message(&response).await; - return; - } - } - } - } + *self.omikron_id.write().await = omikron_id; + *self.challenge.write().await = challenge.clone(); + *self.pub_key.write().await = Some(pub_key_bytes); + *self.identified.write().await = true; - // ONLINE STATUS TRACKING - if cv.is_type(CommunicationType::user_connected) { - log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_user_status( - user_id, - UserStatus::user_online, - omikron_id, - ); - } - return; - } - if cv.is_type(CommunicationType::user_disconnected) { - log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()) { - if let Some(status) = user_online_tracker::get_user_status(user_id) { - user_online_tracker::track_user_status( - user_id, - UserStatus::user_offline, - status.omikron_id, - ); - } - } - return; - } + let encrypted = + encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); - if cv.is_type(CommunicationType::iota_connected) { - log_in!(PrintType::Omega, "IOTA connected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - let mut user_ids = JsonValue::Array(Vec::new()); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - for user in users { - let _ = user_ids.push(JsonValue::from(user.0)); - user_online_tracker::track_user_status( - user.0, - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error, when loading users for IOTA"); - } - self.send_message( - &CommunicationValue::new(CommunicationType::iota_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, user_ids), - ) - .await; - } else { - log_in!(PrintType::General, "No IOTA ID found"); - } - return; - } - if cv.is_type(CommunicationType::iota_disconnected) { - log_in!(PrintType::Omega, "IOTA disconnected"); - - if let Some(v) = cv.get_data(DataTypes::iota_id) { - if let Some(iota_id) = v.as_i64() { - let iota_offline = - user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } - } - } - } - - return; - } - - if cv.is_type(CommunicationType::sync_client_iota_status) { - if let Some(json::JsonValue::Array(user_ids)) = - cv.get_data(DataTypes::user_ids).cloned() - { - for user_id_json in user_ids { - if let Some(user_id) = user_id_json.as_i64() { - user_online_tracker::track_user_status( - user_id, - UserStatus::user_online, - omikron_id, - ); - } - } - } - if let Some(json::JsonValue::Array(iota_ids)) = - cv.get_data(DataTypes::iota_ids).cloned() - { - for iota_id_json in iota_ids { - if let Some(iota_id) = iota_id_json.as_i64() { - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - } - } - } - return; - } - - // DATA RETURN - if cv.is_type(CommunicationType::get_user_data) { - if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { - if let Some(user_id) = user_id.as_i64() { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = get_by_user_id(user_id).await - { - let mut response = - CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username.clone()) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - - if let Some(display) = display { - if display.is_empty() { - response = response.add_data_str(DataTypes::display, username); - } else { - response = response.add_data_str(DataTypes::display, display); - } - } - if let Some(status) = status { - if !status.is_empty() { - response = response.add_data_str(DataTypes::status, status); - } - } - if let Some(about) = about { - if !about.is_empty() { - response = response.add_data_str(DataTypes::about, about); - } - } - if let Some(avatar) = avatar { - response = - response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); - } - - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .into_iter() - .map(|id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - - if let Some(user_status) = user_status { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(user_status.connection_type.to_string()), - ); - response = response.add_data( - DataTypes::omikron_id, - JsonValue::Number(Number::from(user_status.omikron_id)), - ); - } else { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(UserStatus::iota_offline.to_string()), - ); - } - - self.send_message(&response).await; - return; - } - } - } - if let Some(username) = cv.get_data(DataTypes::username).cloned() { - if let Some(username) = username.as_str() { - if let Ok(( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = get_by_username(username).await - { - let mut response = - CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::username, username.clone()) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::user_id, JsonValue::Number(Number::from(id))) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ) - .add_data( - DataTypes::sub_level, - JsonValue::Number(Number::from(sub_level)), - ) - .add_data( - DataTypes::sub_end, - JsonValue::Number(Number::from(sub_end)), - ); - - if let Some(display) = display { - if display.is_empty() { - response = response.add_data_str(DataTypes::display, username); - } else { - response = response.add_data_str(DataTypes::display, display); - } - } - if let Some(status) = status { - if !status.is_empty() { - response = response.add_data_str(DataTypes::status, status); - } - } - if let Some(about) = about { - if !about.is_empty() { - response = response.add_data_str(DataTypes::about, about); - } - } - if let Some(avatar) = avatar { - response = - response.add_data_str(DataTypes::avatar, STANDARD.encode(avatar)); - } - - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - if let Some(user_status) = user_status { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(user_status.connection_type.to_string()), - ); - response = response.add_data( - DataTypes::omikron_id, - JsonValue::Number(Number::from(user_status.omikron_id)), - ); - } else { - response = response.add_data( - DataTypes::online_status, - JsonValue::String(UserStatus::iota_offline.to_string()), - ); - } - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - - self.send_message(&response).await; - return; - } - } - } - - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send_message(&response).await; - - return; - } - if cv.is_type(CommunicationType::get_iota_data) { - if let Some(iota_id) = cv.get_data(DataTypes::iota_id) { - if let Some(iota_id) = iota_id.as_i64() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let mut response = - CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::public_key, public_key) - .add_data(DataTypes::iota_id, JsonValue::from(iota_id)); - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - self.send_message(&response).await; - return; - } - } - } - if let Some(user_id) = cv.get_data(DataTypes::user_id).cloned() { - if let Some(user_id) = user_id.as_i64() { - if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(user_id).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let mut response = - CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(user_id)), - ) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ); - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - - self.send_message(&response).await; - return; - } - } - } - } - if let Some(username) = cv.get_data(DataTypes::username).cloned() { - if let Some(username) = username.as_str() { - if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_username(username).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let mut response = - CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(cv.get_id()) - .add_data_str(DataTypes::public_key, public_key) - .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(user_id)), - ) - .add_data_str(DataTypes::username, username.to_string()) - .add_data( - DataTypes::iota_id, - JsonValue::Number(Number::from(iota_id)), - ); - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id) - .unwrap_or_default(); - - response = response.add_data( - DataTypes::omikron_connections, - JsonValue::Array( - iota_connections - .iter() - .map(|&id| JsonValue::Number(Number::from(id))) - .collect(), - ), - ); - self.send_message(&response).await; - return; - } - } - } - } - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send_message(&response).await; - - return; - } - - // REGISTERING - if cv.is_type(CommunicationType::get_register) { - let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::get_register) + let response = CommunicationValue::new(CommunicationType::challenge) .with_id(cv.get_id()) .add_data( - DataTypes::user_id, - JsonValue::Number(Number::from(register_id)), - ); - self.send_message(&response).await; + DataTypes::public_key, + DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + + let _ = self.send(&response).await; return; } - if cv.is_type(CommunicationType::complete_register_iota) { - let iota_id_opt = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()); - if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { - if let Some(iota_id) = iota_id_opt { - match sql::register_complete_iota(iota_id, public_key.to_string()).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; - } - } - } else { - // New logic to create iota and return id - match sql::create_new_iota(public_key.to_string()).await { - Ok(new_iota_id) => { - let response = - CommunicationValue::new(CommunicationType::complete_register_iota) - .with_id(cv.get_id()) - .add_data(DataTypes::iota_id, JsonValue::from(new_iota_id)); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; - } - } - } + if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { + let client_response = cv.get_data(DataTypes::challenge).as_str().unwrap_or(""); + + if client_response == *self.challenge.read().await { + *self.challenged.write().await = true; + + omikron_manager::add_omikron(self.clone()).await; + + let _ = self + .send( + &CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, DataValue::BoolTrue), + ) + .await; + + log_in!(PrintType::Omega, "Omikron Connected"); } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error_invalid_challenge) + .with_id(cv.get_id()), + ) .await; } - return; } - if cv.is_type(CommunicationType::complete_register_user) { - if let ( - Some(user_id), - Some(username), - Some(public_key), - Some(iota_id), - Some(reset_token), - ) = ( - cv.get_data(DataTypes::user_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::username).and_then(|v| v.as_str()), - cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()), - cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), - ) { - match sql::register_complete_user( - user_id, - username.to_string(), - public_key.to_string(), - iota_id, - reset_token.to_string(), - ) - .await - { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } - Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) + } + + async fn handle_authenticated(self: &Arc, cv: CommunicationValue) { + if cv.is_type(CommunicationType::shorten_link) { + if let Some(link) = cv.get_data(DataTypes::link).as_str() { + if let Ok(short) = add_short_link(link).await { + let _ = self + .send( + &CommunicationValue::new(CommunicationType::shorten_link) .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + .add_data(DataTypes::link, DataValue::Str(short)), ) .await; - } } - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; } return; } - // CHANGING DATA - if cv.is_type(CommunicationType::change_user_data) { - let user_id = cv.get_sender(); - let mut success = true; - let mut error_message = String::new(); - - if let Some(username) = cv.get_data(DataTypes::username).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(display) = cv.get_data(DataTypes::display).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(avatar) = cv.get_data(DataTypes::avatar).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(about) = cv.get_data(DataTypes::about).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(status) = cv.get_data(DataTypes::status).and_then(|v| v.as_str()) { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(public_key) = cv.get_data(DataTypes::public_key).and_then(|v| v.as_str()) { - if let Some(private_key_hash) = cv - .get_data(DataTypes::private_key_hash) - .and_then(|v| v.as_str()) - { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await - { - success = false; - error_message = e.to_string(); - } - } else { - success = false; - error_message = - "private_key_hash is required when changing public_key".to_string(); - } - } - - if success { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_message( - &CommunicationValue::new(CommunicationType::error) + if cv.is_type(CommunicationType::get_register) { + let register_id = sql::get_register_id().await; + let _ = self + .send( + &CommunicationValue::new(CommunicationType::get_register) .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, error_message), + .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)), ) .await; - } return; } - if cv.is_type(CommunicationType::change_iota_data) { - if let (user_id, Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_sender(), - cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()), - cv.get_data(DataTypes::reset_token).and_then(|v| v.as_str()), - cv.get_data(DataTypes::new_token).and_then(|v| v.as_str()), - ) { - match sql::get_by_user_id(user_id).await { - Ok(user) => { - let current_token = user.11; - if current_token == reset_token { - let mut success = true; - let mut error_message = String::new(); - if let Err(e) = sql::change_iota_id(user_id, iota_id).await { - success = false; - error_message = e.to_string(); - } - if success { - if let Err(e) = - sql::change_token(user_id, new_token.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - if success { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send_message(&response).await; - } else { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, error_message), - ) - .await; - } - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await; - } - } - Err(_) => { - self.send_error_response(&cv.get_id(), CommunicationType::error_not_found) - .await; - } - } - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) - .await; - } - return; - } if cv.is_type(CommunicationType::delete_user) { let user_id = cv.get_sender(); - match sql::delete_user(user_id).await { + match sql::delete_user(user_id as i64).await { Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; + let _ = self + .send( + &CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()), + ) + .await; } Err(e) => { - self.send_message( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), - ) - .await; + let _ = self + .send( + &CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), + ) + .await; } } return; } + if cv.is_type(CommunicationType::delete_iota) { - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).and_then(|v| v.as_i64()) { - match sql::delete_iota(iota_id).await { + if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { + match sql::delete_iota(*iota_id).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success) .with_id(cv.get_id()); - self.send_message(&response).await; + self.send(&response).await; } Err(e) => { - self.send_message( + self.send( &CommunicationValue::new(CommunicationType::error) .with_id(cv.get_id()) - .add_data_str(DataTypes::error_type, e.to_string()), + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), ) .await; } } } else { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) .await; } return; @@ -885,85 +324,68 @@ impl OmikronConnection { // NOTIFICATIONS if cv.is_type(CommunicationType::get_notifications) { let user_id = cv.get_sender(); - if let Ok(notifications) = sql::get_notifications(user_id).await { + if let Ok(notifications) = sql::get_notifications(user_id as i64).await { let mut json_array = Vec::new(); for (sender, amount) in notifications { - let mut obj = JsonValue::new_object(); - let _ = obj.insert("sender", JsonValue::from(sender)); - let _ = obj.insert("amount", JsonValue::from(amount)); - json_array.push(obj); + let mut obj = Vec::new(); + let _ = obj.push((DataTypes::sender_id, DataValue::Number(sender))); + let _ = obj.push((DataTypes::amount, DataValue::Number(amount))); + json_array.push(DataValue::Container(obj)); } let response = CommunicationValue::new(CommunicationType::get_notifications) .with_id(cv.get_id()) - .add_array(DataTypes::notifications, json_array); - self.send_message(&response).await; + .add_data(DataTypes::notifications, DataValue::Array(json_array)); + self.send(&response).await; } } if cv.is_type(CommunicationType::read_notification) { if let (user_id, Some(other_id)) = ( cv.get_sender(), - cv.get_data(DataTypes::sender_id) - .unwrap_or(&JsonValue::Null) - .as_i64(), + cv.get_data(DataTypes::sender_id).as_number(), ) { - if let Ok(_) = sql::read_notification(user_id, other_id).await { + if let Ok(_) = sql::read_notification(user_id as i64, other_id).await { let response = CommunicationValue::new(CommunicationType::read_notification) .with_id(cv.get_id()); - self.send_message(&response).await; + self.send(&response).await; } } } if cv.is_type(CommunicationType::push_notification) { if let (user_id, Some(other_id)) = ( cv.get_sender(), - cv.get_data(DataTypes::sender_id) - .unwrap_or(&JsonValue::Null) - .as_i64(), + cv.get_data(DataTypes::sender_id).as_number(), ) { - if let Ok(_) = sql::add_notification(user_id, other_id).await { + if let Ok(_) = sql::add_notification(user_id as i64, other_id).await { let response = CommunicationValue::new(CommunicationType::push_notification) .with_id(cv.get_id()); - self.send_message(&response).await; + self.send(&response).await; } } } } - 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; - } - pub async fn close(&self) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - } - pub async fn handle_close(self: Arc) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - } - async fn handle_ping(&self, cv: CommunicationValue) { - 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; + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + if let Ok(val) = last_ping.to_string().parse::() { + *self.ping.write().await = val; } } - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + let _ = self + .send(&CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id())) + .await; + } - self.send_message(&response).await; + pub async fn handle_close(&self) { + if self.is_identified().await { + let omikron_id = self.get_omikron_id().await; + + if omikron_id != 0 { + log_in!(PrintType::Omega, "Omikron Disconnected"); + + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; + } + } } } diff --git a/src/server/server.rs b/src/server/server.rs index 5167444..c1b0e10 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,11 +1,10 @@ use crate::{ log, - server::{api, short_link::get_short_link, socket}, + server::{api, short_link::get_short_link}, util::file_util::get_directory, }; use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; -use actix_web_actors::ws; use rustls::ServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; @@ -42,7 +41,6 @@ pub async fn start(port: u16) -> anyhow::Result<()> { App::new() .route("/api/{path:.*}", web::to(api_handler)) .route("/direct/{path:.*}", web::to(direct_handler)) - .route("/ws/{path:.*}", web::get().to(ws_handler)) }) .bind_rustls_0_23(addr, config)? .run() @@ -64,16 +62,6 @@ async fn direct_handler(req: HttpRequest) -> impl Responder { .finish() } } -async fn ws_handler( - req: HttpRequest, - stream: web::Payload, - path: web::Path, -) -> Result { - let path = path.into_inner(); - println!("WS handler reached: {}", path); - - ws::start(socket::WsSession::new(path), &req, stream) -} async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { let path = req.uri().path().to_string(); diff --git a/src/server/socket.rs b/src/server/socket.rs deleted file mode 100755 index 7d04b26..0000000 --- a/src/server/socket.rs +++ /dev/null @@ -1,129 +0,0 @@ -use std::sync::Arc; -use std::time::{Duration, Instant}; - -use actix::{Actor, ActorContext, AsyncContext, StreamHandler}; -use actix_web_actors::ws; - -use crate::data::communication::{CommunicationType, CommunicationValue}; -use crate::log; -use crate::server::omikron_connection::OmikronConnection; - -const IDLE_TIMEOUT: Duration = Duration::from_secs(30); - -use actix::Message; - -#[derive(Message)] -#[rtype(result = "()")] -pub struct WsSendMessage(pub String); - -impl actix::Handler for WsSession { - type Result = (); - - fn handle(&mut self, msg: WsSendMessage, ctx: &mut Self::Context) { - ctx.text(msg.0); - } -} - -pub struct WsSession { - path: String, - last_heartbeat: Instant, - omikron: Option>, -} - -impl WsSession { - pub fn new(path: String) -> Self { - Self { - path, - last_heartbeat: Instant::now(), - omikron: None, - } - } - - fn start_heartbeat(&self, ctx: &mut ws::WebsocketContext) { - ctx.run_interval(Duration::from_secs(5), |act, ctx| { - if Instant::now().duration_since(act.last_heartbeat) > IDLE_TIMEOUT { - log!("[ws_handler] Heartbeat failed. Disconnecting."); - ctx.close(None); - ctx.stop(); - return; - } - - let ping = CommunicationValue::new(CommunicationType::ping) - .to_json() - .to_string(); - - ctx.text(ping); - }); - } -} - -impl Actor for WsSession { - type Context = ws::WebsocketContext; - - fn started(&mut self, ctx: &mut Self::Context) { - self.start_heartbeat(ctx); - - if self.path == "omikron" { - let addr = ctx.address(); - let connection = OmikronConnection::new(addr); - self.omikron = Some(connection); - } - } - - fn stopped(&mut self, _: &mut Self::Context) { - log!( - "[ws] WebSocket handling task for path: {} is finished.", - self.path - ); - - if let Some(conn) = &self.omikron { - let conn = conn.clone(); - actix_rt::spawn(async move { - conn.handle_close().await; - }); - } - } -} -impl StreamHandler> for WsSession { - fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { - match msg { - Ok(ws::Message::Text(text)) => { - self.last_heartbeat = Instant::now(); - - if let Some(conn) = &self.omikron { - let conn_clone = conn.clone(); - actix_rt::spawn(async move { - conn_clone.handle_message(text.to_string()).await; - }); - } - } - - Ok(ws::Message::Ping(msg)) => { - self.last_heartbeat = Instant::now(); - ctx.pong(&msg); - } - - Ok(ws::Message::Pong(_)) => { - self.last_heartbeat = Instant::now(); - log!("[ws_handler] Received Pong. Connection alive."); - } - - Ok(ws::Message::Close(reason)) => { - log!("[ws_handler] Received Close. Disconnecting."); - ctx.close(reason); - ctx.stop(); - } - - Ok(ws::Message::Binary(_)) => { - log!("[ws_handler] Binary message ignored."); - } - - Err(e) => { - log!("[ERROR] WS Error: {}. Closing.", e); - ctx.stop(); - } - - _ => {} - } - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index 80bc7c6..e861e69 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -8,10 +9,9 @@ use std::{ }; use ansi_term::Color; +use epsilon_core::{CommunicationValue, DataTypes, DataValue}; use json::JsonValue; -use crate::data::communication::CommunicationValue; - static LOGGER: OnceLock> = OnceLock::new(); #[derive(Clone, Copy)] @@ -224,7 +224,7 @@ pub fn log_cv_internal( let formatted = format_cv(cv); log_internal( - Some(cv.get_sender()), + Some(cv.get_sender() as i64), print_type.unwrap_or(PrintType::General), prefix, false, @@ -249,24 +249,79 @@ pub fn format_cv(cv: &CommunicationValue) -> String { let comm_type = cv.get_type().to_string(); parts.push(format!("{}", comm_type)); - let mut data_parts = Vec::new(); - if let JsonValue::Object(data) = &cv.clone().to_json()["data"] { - for (key, value) in data.iter() { - let val_string = match value { - JsonValue::String(s) => s.clone(), - _ => value.dump(), - }; + let data: &HashMap = cv.get_data_container(); - data_parts.push(format!("{} {}", key, val_string)); - } - } + let formated_data = + format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); - if !data_parts.is_empty() { - parts.push(format!("{}", data_parts.join(", "))); - } + parts.push(format!("{}", formated_data)); parts.join(": ") } + +fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { + let parts: Vec = data + .into_iter() + .map(|(key, value)| { + let key_str = key.to_string(); + + match value { + DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{}={{ {} }}", key_str, inner_formatted) + } + + DataValue::Array(arr) => { + let arr_formatted = format_array(arr); + format!("{}=[{}]", key_str, arr_formatted) + } + + DataValue::Bool(b) => format!("{}={}", key_str, b), + + DataValue::BoolTrue => format!("{}=true", key_str), + DataValue::BoolFalse => format!("{}=false", key_str), + + DataValue::Number(num) => format!("{}={}", key_str, num), + + _ => "".to_string(), + } + }) + .collect(); + + parts.join(", ") +} + +fn format_array(arr: Vec) -> String { + let parts: Vec = arr + .into_iter() + .map(|value| match value { + DataValue::Str(s) => format!("\"{}\"", s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{{ {} }}", inner_formatted) + } + + DataValue::Array(inner_arr) => { + let formatted = format_array(inner_arr); + format!("[{}]", formatted) + } + + DataValue::Bool(b) => b.to_string(), + + DataValue::BoolTrue => "true".to_string(), + DataValue::BoolFalse => "false".to_string(), + + DataValue::Number(num) => num.to_string(), + + _ => String::new(), + }) + .collect(); + + parts.join(", ") +} #[macro_export] macro_rules! log_cv { ($kind:expr, $cv:expr) => { From 6099b0d636359ca86953f6eb158c455839b0049b Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:30:35 +0100 Subject: [PATCH 079/220] [WIP] QUIC connection --- Cargo.lock | 599 +------------ Cargo.toml | 43 +- src/main.rs | 6 +- src/server/api.rs | 22 +- src/server/omikron_connection.rs | 1370 +++++++++++++++++++++++------- src/server/omikron_manager.rs | 8 +- 6 files changed, 1123 insertions(+), 925 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40c314f..833d221 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,7 @@ dependencies = [ "ansi_term", "anyhow", "async-trait", - "async-tungstenite", - "axum", - "base64 0.22.1", + "base64", "bytes", "color-eyre", "dashmap", @@ -28,9 +26,6 @@ dependencies = [ "hex", "hkdf", "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", "json", "once_cell", "pnet", @@ -38,14 +33,15 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "reqwest", - "rustls 0.23.37", - "rustls-pemfile 2.2.0", + "rustls", + "rustls-pemfile", "sha1", "sha2", "sqlx", "strum 0.27.2", "strum_macros 0.27.2", "sysinfo", + "thiserror 2.0.18", "tokio", "tokio-rustls", "tokio-tungstenite", @@ -111,7 +107,7 @@ dependencies = [ "actix-service", "actix-tls", "actix-utils", - "base64 0.22.1", + "base64", "bitflags 2.11.0", "brotli", "bytes", @@ -531,18 +527,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-native-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" -dependencies = [ - "futures-util", - "native-tls", - "thiserror 1.0.69", - "url", -] - [[package]] name = "async-std" version = "1.13.2" @@ -575,19 +559,6 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" -[[package]] -name = "async-tls" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e" -dependencies = [ - "futures-core", - "futures-io", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "webpki-roots 0.22.6", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -599,36 +570,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-tungstenite" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4" -dependencies = [ - "async-native-tls", - "async-std", - "async-tls", - "atomic-waker", - "futures-core", - "futures-io", - "futures-task", - "futures-util", - "gio", - "glib", - "log", - "native-tls", - "openssl", - "pin-project-lite", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-native-tls", - "tokio-openssl", - "tokio-rustls", - "tungstenite", - "webpki-roots 1.0.6", -] - [[package]] name = "atoi" version = "2.0.0" @@ -652,9 +593,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", "zeroize", @@ -662,9 +603,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" dependencies = [ "cc", "cmake", @@ -672,61 +613,6 @@ dependencies = [ "fs_extra", ] -[[package]] -name = "axum" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" -dependencies = [ - "axum-core", - "base64 0.22.1", - "bytes", - "form_urlencoded", - "futures-util", - "http 1.4.0", - "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", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http 1.4.0", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.76" @@ -742,12 +628,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -872,16 +752,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-expr" -version = "0.20.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -1247,9 +1117,10 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" dependencies = [ "byteorder", + "quinn", "rand 0.8.5", "strum 0.28.0", "strum_macros 0.28.0", @@ -1258,14 +1129,14 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" dependencies = [ "anyhow", "async-trait", "bytes", "epsilon-core", "quinn", - "rustls 0.23.37", + "rustls", "thiserror 2.0.18", "tokio", ] @@ -1407,21 +1278,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[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" @@ -1630,80 +1486,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "gio" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", -] - -[[package]] -name = "gio-sys" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.61.2", -] - -[[package]] -name = "glib" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" -dependencies = [ - "bitflags 2.11.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", -] - -[[package]] -name = "glib-macros" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" -dependencies = [ - "heck", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" -dependencies = [ - "libc", - "system-deps", -] - [[package]] name = "glob" version = "0.3.3" @@ -1722,17 +1504,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gobject-sys" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "h2" version = "0.3.27" @@ -1924,7 +1695,6 @@ dependencies = [ "http 1.4.0", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -1942,9 +1712,7 @@ dependencies = [ "http 1.4.0", "hyper", "hyper-util", - "log", - "rustls 0.23.37", - "rustls-native-certs", + "rustls", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1957,7 +1725,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -1971,7 +1739,6 @@ dependencies = [ "socket2 0.6.2", "system-configuration", "tokio", - "tower-layer", "tower-service", "tracing", "windows-registry", @@ -2140,9 +1907,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "ipnetwork" @@ -2267,12 +2034,13 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ "bitflags 2.11.0", "libc", + "plain", "redox_syscall 0.7.3", ] @@ -2355,12 +2123,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "md-5" version = "0.10.6" @@ -2405,23 +2167,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "no-std-net" version = "0.6.0" @@ -2529,50 +2274,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags 2.11.0", - "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.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "owo-colors" version = "4.3.0" @@ -2647,9 +2354,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand 2.3.0", @@ -2683,6 +2390,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "pnet" version = "0.35.0" @@ -2856,15 +2569,6 @@ dependencies = [ "syn", ] -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -2886,7 +2590,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.37", + "rustls", "socket2 0.6.2", "thiserror 2.0.18", "tokio", @@ -2908,7 +2612,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "slab", @@ -3071,7 +2775,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", @@ -3088,7 +2792,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "sync_wrapper", @@ -3185,18 +2889,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.37" @@ -3208,7 +2900,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.9", + "rustls-webpki", "subtle", "zeroize", ] @@ -3225,15 +2917,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -3264,10 +2947,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.37", + "rustls", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.9", + "rustls-webpki", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -3280,16 +2963,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.103.9" @@ -3338,16 +3011,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -3420,26 +3083,6 @@ dependencies = [ "zmij", ] -[[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_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3606,7 +3249,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "async-io 1.13.0", "async-std", - "base64 0.22.1", + "base64", "bytes", "crc", "crossbeam-queue", @@ -3677,7 +3320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bitflags 2.11.0", "byteorder", "bytes", @@ -3719,7 +3362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bitflags 2.11.0", "byteorder", "crc", @@ -3898,38 +3541,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "7.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" - -[[package]] -name = "tempfile" -version = "3.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" -dependencies = [ - "fastrand 2.3.0", - "getrandom 0.4.1", - "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -4037,9 +3648,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -4054,43 +3665,22 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" 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-openssl" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" -dependencies = [ - "openssl", - "openssl-sys", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls", "tokio", ] @@ -4123,57 +3713,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.0.9+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - [[package]] name = "tower" version = "0.5.3" @@ -4187,7 +3726,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -4291,13 +3829,9 @@ dependencies = [ "http 1.4.0", "httparse", "log", - "native-tls", "rand 0.9.2", - "rustls 0.23.37", - "rustls-pki-types", "sha1", "thiserror 2.0.18", - "url", "utf-8", ] @@ -4415,12 +3949,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - [[package]] name = "version_check" version = "0.9.5" @@ -4595,16 +4123,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki-root-certs" version = "1.0.6" @@ -4614,24 +4132,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "whoami" version = "1.6.1" @@ -5107,15 +4607,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -5367,9 +4858,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 8432d26..a5cb77f 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,39 +15,6 @@ aes-gcm = "*" ansi_term = "0.12.1" anyhow = "1.0.101" async-trait = "0.1.89" -async-tungstenite = { version = "0.32.0", features = [ - "futures-03-sink", - "futures-util", - "handshake", - "__rustls-tls", - "async-native-tls", - "async-std", - "async-std-runtime", - "async-tls", - "gio", - "gio-runtime", - "glib", - "openssl", - "real-async-native-tls", - "real-async-tls", - "real-native-tls", - "real-tokio-native-tls", - "real-tokio-openssl", - "real-tokio-rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-native-tls", - "tokio-openssl", - "tokio-runtime", - "tokio-rustls-manual-roots", - "tokio-rustls-native-certs", - "tokio-rustls-webpki-roots", - "url", - "verbose-logging", - "webpki-roots", -] } -axum = { version = "0.8.8", features = ["ws", "http2"] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" @@ -58,16 +25,14 @@ futures-util = "0.3.31" hex = "0.4.3" hkdf = "0.12.4" http-body-util = "0.1.3" -hyper = { version = "1.8.1", features = ["http2", "full"] } -hyper-rustls = { version = "0.27.7", features = ["http2"] } -hyper-util = { version = "0.1.20", features = ["full"] } json = "0.12.4" once_cell = "1.21.3" pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = "0.13.2" -rustls = "0.23.37" +reqwest = { version = "0.13.2" } +rustls = { version = "0.23.37", default-features = false, features = ["std", "tls12", "aws-lc-rs", "prefer-post-quantum"] } +quinn = { version = "0.11.9", default-features = false, features = ["runtime-tokio", "rustls-aws-lc-rs"] } rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" @@ -85,4 +50,4 @@ uuid = { version = "1.19.0", features = ["v4"] } walkdir = "2.5.0" x448 = "0.6.0" zip = "6.0.0" -quinn = "0.11.9" +thiserror = "2.0.18" diff --git a/src/main.rs b/src/main.rs index 043700c..72c42c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,9 @@ async fn main() { startup(); log_in!("Incoming messages"); log_out!("Outgoing messages"); + + let _ = omikron_connection::start(9187).await; + log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { @@ -43,7 +46,8 @@ async fn main() { } else { log!(" Users"); } + let _ = server::server::start(9188).await; - let _ = omikron_connection::OmikronServer::start(9187).await; + tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/api.rs b/src/server/api.rs index 7f3c728..b216112 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -65,16 +65,20 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { // ================================================== ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { - let id = omikron_conn.get_omikron_id().await; + if let Some(id) = omikron_conn.get_omikron_id().await { + if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); - if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - - (StatusCode::OK, res.dump()) + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } } else { let mut res = JsonValue::new_object(); res["status"] = "error".into(); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index ade9746..b509787 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,412 +1,1140 @@ -use crate::server::{omikron_manager, short_link::add_short_link}; -use crate::sql::{sql, sql::get_omikron_by_id, user_online_tracker}; -use crate::util::file_util::load_file_buf; -use crate::util::{crypto_helper::encrypt, logger::PrintType}; -use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in}; +use crate::{ + get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, + server::{omikron_manager, short_link::add_short_link}, + sql::{ + connection_status::UserStatus, + sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, + user_online_tracker::{self}, + }, + util::{ + crypto_helper::encrypt, + file_util::{load_file_buf, load_file_vec}, + logger::PrintType, + }, +}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use epsilon_native::{Receiver, Sender, host}; +use epsilon_native::{Host, Receiver, Sender}; use quinn::ServerConfig; use rand::{Rng, distributions::Alphanumeric}; -use rustls::pki_types::PrivateKeyDer; -use std::sync::Arc; -use tokio::sync::RwLock; +use rustls::{ServerConfig as CryptoConfig, crypto::aws_lc_rs}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::{ + sync::{Mutex, RwLock}, + time::interval, +}; use x448::PublicKey; -pub struct OmikronServer; +// ============================================================================ +// Configuration +// ============================================================================ -impl OmikronServer { - pub async fn start(port: u16) -> Result<(), Box> { - let tls_cfg = OmikronServer::load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg) - .expect("Failed to convert to QuicServerConfig"); - let mut host = host(port, ServerConfig::with_crypto(Arc::new(server_crypto))).await?; - tokio::spawn(async move { - while let Some((sender, receiver)) = host.next().await { - let connection = OmikronConnection::new(sender); - tokio::spawn(Self::connection_loop(connection, receiver)); +const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); +const MAX_WAITING_AGE: Duration = Duration::from_secs(60); + +// ============================================================================ +// Error Types +// ============================================================================ + +#[derive(Debug, thiserror::Error)] +pub enum OmikronError { + #[error("Not connected")] + NotConnected, + #[error("Not authenticated")] + NotAuthenticated, + #[error("Invalid response")] + InvalidResponse, + #[error("Authentication failed")] + AuthenticationFailed, + #[error("SQL error: {0}")] + Sql(String), + #[error("Send error: {0}")] + Send(String), +} + +pub type OmikronResult = Result; + +// ============================================================================ +// Waiting Task System (Preserved from original) +// ============================================================================ + +pub struct WaitingTask { + pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub inserted_at: Instant, +} + +// ============================================================================ +// Connection State +// ============================================================================ + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum AuthState { + Unauthenticated, + Identified { omikron_id: i64 }, + Authenticated { omikron_id: i64 }, +} + +impl AuthState { + fn is_authenticated(&self) -> bool { + match self { + AuthState::Authenticated { omikron_id } => true, + _ => false, + } + } + + fn omikron_id(&self) -> Option { + match self { + AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { + Some(*omikron_id) } - }); - - Ok(()) - } - fn load_tls() -> Option { - let mut cert_file_buf = load_file_buf("certs", "cert.pem").ok()?; - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; - - let cert_chain = rustls_pemfile::certs(&mut cert_file_buf) - .collect::, _>>() - .ok()?; - - let mut keys: Vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; - - if keys.is_empty() { - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; - keys = rustls_pemfile::rsa_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; + _ => None, } - - if keys.is_empty() { - return None; - } - - let cfg = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, keys.remove(0)) - .ok()?; - - Some(cfg) - } - - async fn connection_loop(conn: Arc, receiver: Receiver) { - while let Ok(cv) = receiver.receive().await { - log_cv_in!(PrintType::Omikron, cv); - conn.clone().handle_message(cv).await; - } - - conn.handle_close().await; } } +// ============================================================================ +// Omikron Connection (Epsilon/QUIC-based) +// ============================================================================ + pub struct OmikronConnection { - sender: Arc>>, - omikron_id: Arc>, - pub_key: Arc>>>, + id: u64, + sender: Mutex>, + state: RwLock, - identified: Arc>, - challenged: Arc>, - challenge: Arc>, + // Authentication state (preserved from original) + challenge: RwLock, + pub_key: RwLock>>, - ping: Arc>, + // Ping tracking + pub ping: RwLock, - waiting_tasks: - DashMap, CommunicationValue) -> bool + Send + Sync>>, + // Waiting tasks for request/response pattern + waiting_tasks: DashMap, + + // Cleanup handle + cleanup_handle: Mutex>>, } impl OmikronConnection { + // ------------------------------------------------------------------------- + // Construction + // ------------------------------------------------------------------------- + pub fn new(sender: Sender) -> Arc { - Arc::new(Self { - sender: Arc::new(RwLock::new(Some(sender))), - omikron_id: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - identified: Arc::new(RwLock::new(false)), - challenged: Arc::new(RwLock::new(false)), - challenge: Arc::new(RwLock::new(String::new())), - ping: Arc::new(RwLock::new(-1)), + let conn = Arc::new(Self { + id: rand::random(), + sender: Mutex::new(Some(sender)), + state: RwLock::new(AuthState::Unauthenticated), + challenge: RwLock::new(String::new()), + pub_key: RwLock::new(None), + ping: RwLock::new(-1), waiting_tasks: DashMap::new(), - }) + cleanup_handle: Mutex::new(None), + }); + + // Start cleanup task for waiting tasks + let cleanup_conn = conn.clone(); + let handle = tokio::spawn(async move { + let mut ticker = interval(CLEANUP_INTERVAL); + loop { + ticker.tick().await; + cleanup_conn + .waiting_tasks + .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + } + }); + + // Store handle (would need block_in_place or similar to set this immediately) + // For now, we'll handle this differently in handle() + + conn } - async fn send(&self, cv: &CommunicationValue) { - log_cv_out!(PrintType::Omikron, cv); + // ------------------------------------------------------------------------- + // Main Handler Loop + // ------------------------------------------------------------------------- - if let Some(sender) = self.sender.read().await.as_ref() { - if sender.send(cv).await.is_err() { - self.handle_close().await; + pub async fn handle(self: Arc, mut receiver: Receiver) { + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection started" + ); + + // Start cleanup task + let cleanup_conn = self.clone(); + let _cleanup_handle = tokio::spawn(async move { + let mut ticker = interval(CLEANUP_INTERVAL); + loop { + ticker.tick().await; + cleanup_conn + .waiting_tasks + .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + } + }); + + while let Ok(cv) = receiver.receive().await { + if let Err(e) = self.clone().process_message(cv).await { + log_err!(0, PrintType::Omega, "Error processing message: {}", e); + // Don't break on error unless critical - match original WebSocket behavior + if matches!(e, OmikronError::NotConnected) { + break; + } + } + } + + // Connection closed + self.cleanup().await; + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection closed" + ); + } + + // ------------------------------------------------------------------------- + // Message Processing + // ------------------------------------------------------------------------- + + async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + // Log incoming + log_cv_in!(PrintType::Omikron, &cv); + + let msg_id = cv.get_id(); + + // Check waiting tasks first (response to previous request) + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + let _ = (task.task)(self.clone(), cv); + return Ok(()); + } + + // Handle ping regardless of auth state + if cv.is_type(CommunicationType::ping) { + return self.handle_ping(cv).await; + } + + // Route based on authentication state + match *self.state.read().await { + AuthState::Unauthenticated => self.handle_unauthenticated(cv).await, + AuthState::Identified { .. } => self.handle_identified(cv).await, + AuthState::Authenticated { omikron_id } => { + self.handle_authenticated(cv, omikron_id).await } } } - async fn send_error_response(&self, id: u32, comm_type: CommunicationType) { - let response = CommunicationValue::new(comm_type).with_id(id); - self.send(&response).await; - } + // ------------------------------------------------------------------------- + // Authentication Handlers + // ------------------------------------------------------------------------- - pub async fn get_omikron_id(&self) -> i64 { - *self.omikron_id.read().await - } - - async fn is_identified(&self) -> bool { - *self.identified.read().await && *self.challenged.read().await - } - - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; + async fn handle_unauthenticated(&self, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::identification) { + self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); } - if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { - let _ = task(self.clone(), cv.clone()); - return; - } + // Extract omikron ID + let omikron_id = cv + .get_data(DataTypes::omikron) + .as_number() + .ok_or(OmikronError::InvalidResponse)?; - if !self.is_identified().await { - self.handle_identification(cv).await; - return; - } + // Lookup omikron in database + let (public_key, _) = get_omikron_by_id(omikron_id) + .await + .map_err(|e| OmikronError::Sql(e.to_string()))?; - self.handle_authenticated(cv).await; + let pub_key_bytes = STANDARD + .decode(&public_key) + .map_err(|_| OmikronError::AuthenticationFailed)?; + + let omikron_pub_key = + PublicKey::from_bytes(&pub_key_bytes).ok_or(OmikronError::AuthenticationFailed)?; + + // Generate challenge + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + // Store state + *self.challenge.write().await = challenge.clone(); + *self.pub_key.write().await = Some(pub_key_bytes); + *self.state.write().await = AuthState::Identified { omikron_id }; + + // Encrypt challenge + let encrypted = encrypt(get_private_key(), omikron_pub_key, &challenge) + .map_err(|_| OmikronError::AuthenticationFailed)?; + + // Send challenge response + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + + self.send(&response).await } - async fn handle_identification(self: &Arc, cv: CommunicationValue) { - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; + async fn handle_identified(&self, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::challenge_response) { + self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); + } - if !identified && cv.is_type(CommunicationType::identification) { - let omikron_id = cv.get_data(DataTypes::omikron).as_number().unwrap_or(0); + let client_response = cv + .get_data(DataTypes::challenge) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; - let (public_key, _) = match get_omikron_by_id(omikron_id).await { - Ok(v) => v, - Err(_) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_not_authenticated) - .with_id(cv.get_id()), - ) - .await; - return; - } - }; + let expected_challenge = self.challenge.read().await.clone(); - let pub_key_bytes = match STANDARD.decode(&public_key) { - Ok(b) => b, - Err(_) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_invalid_omikron_id) - .with_id(cv.get_id()), - ) - .await; - return; - } - }; + if client_response == expected_challenge { + // Challenge passed - mark as authenticated + let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); + *self.state.write().await = AuthState::Authenticated { omikron_id }; - let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { - Some(k) => k, - _ => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_invalid_public_key) - .with_id(cv.get_id()), - ) - .await; - return; - } - }; + // Register with manager + omikron_manager::add_omikron(self.arc_self()).await; - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.omikron_id.write().await = omikron_id; - *self.challenge.write().await = challenge.clone(); - *self.pub_key.write().await = Some(pub_key_bytes); - *self.identified.write().await = true; - - let encrypted = - encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); - - let response = CommunicationValue::new(CommunicationType::challenge) + // Send success response + let response = CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + .add_data(DataTypes::accepted, DataValue::Bool(true)); + + self.send(&response).await?; + log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); + Ok(()) + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + .await; + Err(OmikronError::AuthenticationFailed) + } + } + + // ------------------------------------------------------------------------- + // Authenticated Message Handlers + // ------------------------------------------------------------------------- + + async fn handle_authenticated( + &self, + cv: CommunicationValue, + omikron_id: i64, + ) -> OmikronResult<()> { + match cv.get_type() { + // Link shortening + CommunicationType::shorten_link => self.handle_shorten_link(cv).await, + + // Online status tracking + CommunicationType::user_connected => { + self.handle_user_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::user_disconnected => { + self.handle_user_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_connected => { + self.handle_iota_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_disconnected => { + self.handle_iota_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::sync_client_iota_status => { + self.handle_sync_status(cv, omikron_id).await; + Ok(()) + } + + // Data queries + CommunicationType::get_user_data => self.handle_get_user_data(cv).await, + CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, + + // Registration + CommunicationType::get_register => self.handle_get_register(cv).await, + CommunicationType::complete_register_iota => { + self.handle_complete_register_iota(cv).await + } + CommunicationType::complete_register_user => { + self.handle_complete_register_user(cv).await + } + + // Data modification + CommunicationType::change_user_data => self.handle_change_user_data(cv).await, + CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, + CommunicationType::delete_user => self.handle_delete_user(cv).await, + CommunicationType::delete_iota => self.handle_delete_iota(cv).await, + + // Notifications + CommunicationType::get_notifications => self.handle_get_notifications(cv).await, + CommunicationType::read_notification => self.handle_read_notification(cv).await, + CommunicationType::push_notification => self.handle_push_notification(cv).await, + + _ => { + log_err!( + 0, + PrintType::Omega, + "Unknown message type: {:?}", + cv.get_type() + ); + Ok(()) + } + } + } + + // ------------------------------------------------------------------------- + // Specific Handlers (ported from original WebSocket implementation) + // ------------------------------------------------------------------------- + + async fn handle_shorten_link(&self, cv: CommunicationValue) -> OmikronResult<()> { + let link = cv + .get_data(DataTypes::link) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; + + let short = add_short_link(link) + .await + .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; + + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, DataValue::Str(short)); + + self.send(&response).await + } + + async fn handle_user_connected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "User connected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_online, + omikron_id, + ); + } + } + + async fn handle_user_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "User disconnected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_offline, + status.omikron_id, + ); + } + } + } + + async fn handle_iota_connected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA connected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + + let mut user_ids = Vec::new(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { + user_ids.push(DataValue::Number(user_id)); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_offline, + omikron_id, + ); + } + } else { + log_in!(PrintType::General, "SQL error loading users for IOTA"); + } + + let response = CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); let _ = self.send(&response).await; - return; + } else { + log_in!(PrintType::General, "No IOTA ID found"); } + } - if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { - let client_response = cv.get_data(DataTypes::challenge).as_str().unwrap_or(""); - - if client_response == *self.challenge.read().await { - *self.challenged.write().await = true; - - omikron_manager::add_omikron(self.clone()).await; - - let _ = self - .send( - &CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::BoolTrue), - ) - .await; - - log_in!(PrintType::Omega, "Omikron Connected"); - } else { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_invalid_challenge) - .with_id(cv.get_id()), - ) - .await; + async fn handle_iota_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA disconnected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); + } } } } - async fn handle_authenticated(self: &Arc, cv: CommunicationValue) { - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link).as_str() { - if let Ok(short) = add_short_link(link).await { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, DataValue::Str(short)), - ) - .await; + async fn handle_sync_status(&self, cv: CommunicationValue, omikron_id: i64) { + if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { + for user_id_val in user_ids { + if let DataValue::Number(user_id) = user_id_val { + user_online_tracker::track_user_status( + *user_id, + UserStatus::user_online, + omikron_id, + ); } } - return; } - if cv.is_type(CommunicationType::get_register) { - let register_id = sql::get_register_id().await; - let _ = self - .send( - &CommunicationValue::new(CommunicationType::get_register) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)), - ) - .await; - return; - } - - if cv.is_type(CommunicationType::delete_user) { - let user_id = cv.get_sender(); - match sql::delete_user(user_id as i64).await { - Ok(_) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()), - ) - .await; - } - Err(e) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), - ) - .await; + if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { + for iota_id_val in iota_ids { + if let DataValue::Number(iota_id) = iota_id_val { + user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); } } - return; + } + } + + async fn handle_get_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + // Try by user_id first + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok(user_data) = get_by_user_id(user_id as i64).await { + let response = self.build_user_data_response(cv.get_id(), user_data).await; + return self.send(&response).await; + } } - if cv.is_type(CommunicationType::delete_iota) { - if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { - match sql::delete_iota(*iota_id).await { + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok(user_data) = get_by_username(username).await { + let response = self.build_user_data_response(cv.get_id(), user_data).await; + return self.send(&response).await; + } + } + + // Not found + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_user_data_response( + &self, + msg_id: u32, + user: ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + ) -> CommunicationValue { + let ( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + ) = user; + + let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(msg_id) + .add_data(DataTypes::username, DataValue::Str(username.clone())) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::user_id, DataValue::Number(id)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) + .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) + .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); + + // Display name (fallback to username) + let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); + response = response.add_data(DataTypes::display, DataValue::Str(display_name)); + + // Optional fields + if let Some(s) = status.filter(|s| !s.is_empty()) { + response = response.add_data(DataTypes::status, DataValue::Str(s)); + } + if let Some(a) = about.filter(|a| !a.is_empty()) { + response = response.add_data(DataTypes::about, DataValue::Str(a)); + } + if let Some(av) = avatar { + response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); + } + + // Online status + let user_status = user_online_tracker::get_user_status(id); + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + if let Some(us) = user_status { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(us.connection_type.to_string()), + ); + response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); + } else { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(UserStatus::iota_offline.to_string()), + ); + } + + response = response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ); + + response + } + + async fn handle_get_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + // Try by iota_id + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; + } + } + + // Try by user_id + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id as i64).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id as i64), + None, + ) + .await; + return self.send(&response).await; + } + } + } + + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_username(username).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id), + Some(username.to_string()), + ) + .await; + return self.send(&response).await; + } + } + } + + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_iota_data_response( + &self, + msg_id: u32, + iota_id: i64, + public_key: String, + user_id: Option, + username: Option, + ) -> CommunicationValue { + let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(msg_id) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + + if let Some(uid) = user_id { + response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); + } + if let Some(uname) = username { + response = response.add_data(DataTypes::username, DataValue::Str(uname)); + } + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ) + } + + async fn handle_get_register(&self, cv: CommunicationValue) -> OmikronResult<()> { + let register_id = sql::get_register_id().await; + let response = CommunicationValue::new(CommunicationType::get_register) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); + self.send(&response).await + } + + async fn handle_complete_register_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + let iota_id_opt = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + + if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { + if let Some(iota_id) = iota_id_opt { + // Register existing IOTA + match sql::register_complete_iota(iota_id, public_key.to_string()).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success) .with_id(cv.get_id()); - self.send(&response).await; + self.send(&response).await } Err(e) => { - self.send( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), - ) - .await; + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await; + // Create new IOTA + match sql::create_new_iota(public_key.to_string()).await { + Ok(new_iota_id) => { + let response = + CommunicationValue::new(CommunicationType::complete_register_iota) + .with_id(cv.get_id()) + .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_complete_register_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv + .get_data(DataTypes::user_id) + .as_number() + .map(|n| n as i64); + let username = cv + .get_data(DataTypes::username) + .as_str() + .map(|s| s.to_string()); + let public_key = cv + .get_data(DataTypes::public_key) + .as_str() + .map(|s| s.to_string()); + let iota_id = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + let reset_token = cv + .get_data(DataTypes::reset_token) + .as_str() + .map(|s| s.to_string()); + + if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = + (user_id, username, public_key, iota_id, reset_token) + { + match sql::register_complete_user(uid, uname, pk, iid, rt).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_change_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + let mut success = true; + let mut error_message = String::new(); + + // Process each field + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(display) = cv.get_data(DataTypes::display).as_str() { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(about) = cv.get_data(DataTypes::about).as_str() { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(status) = cv.get_data(DataTypes::status).as_str() { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let (Some(public_key), Some(private_key_hash)) = ( + cv.get_data(DataTypes::public_key).as_str(), + cv.get_data(DataTypes::private_key_hash).as_str(), + ) { + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), + ) + .await + { + success = false; + error_message = e.to_string(); } - return; } - // NOTIFICATIONS - if cv.is_type(CommunicationType::get_notifications) { - let user_id = cv.get_sender(); - if let Ok(notifications) = sql::get_notifications(user_id as i64).await { - let mut json_array = Vec::new(); - for (sender, amount) in notifications { - let mut obj = Vec::new(); - let _ = obj.push((DataTypes::sender_id, DataValue::Number(sender))); - let _ = obj.push((DataTypes::amount, DataValue::Number(amount))); - json_array.push(DataValue::Container(obj)); + if success { + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } + + async fn handle_change_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + + if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64), + cv.get_data(DataTypes::reset_token).as_str(), + cv.get_data(DataTypes::new_token).as_str(), + ) { + match sql::get_by_user_id(user_id).await { + Ok(user) => { + let current_token = user.11; // reset_token field + if current_token == reset_token { + let mut success = true; + let mut error_message = String::new(); + + if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + success = false; + error_message = e.to_string(); + } + if success { + if let Err(e) = sql::change_token(user_id, new_token.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } else { + self.send_error_response( + cv.get_id(), + CommunicationType::error_invalid_challenge, + ) + .await + } } - let response = CommunicationValue::new(CommunicationType::get_notifications) + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error_not_found) + .await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_delete_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + match sql::delete_user(user_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(json_array)); - self.send(&response).await; - } - } - if cv.is_type(CommunicationType::read_notification) { - if let (user_id, Some(other_id)) = ( - cv.get_sender(), - cv.get_data(DataTypes::sender_id).as_number(), - ) { - if let Ok(_) = sql::read_notification(user_id as i64, other_id).await { - let response = CommunicationValue::new(CommunicationType::read_notification) - .with_id(cv.get_id()); - self.send(&response).await; - } - } - } - if cv.is_type(CommunicationType::push_notification) { - if let (user_id, Some(other_id)) = ( - cv.get_sender(), - cv.get_data(DataTypes::sender_id).as_number(), - ) { - if let Ok(_) = sql::add_notification(user_id as i64, other_id).await { - let response = CommunicationValue::new(CommunicationType::push_notification) - .with_id(cv.get_id()); - self.send(&response).await; - } + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await } } } - 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; - } - pub async fn close(&self) { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - pub async fn handle_close(self: Arc) { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; + async fn handle_delete_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + if let Some(iota_id) = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64) + { + match sql::delete_iota(iota_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await } } - async fn handle_ping(&self, cv: CommunicationValue) { + async fn handle_get_notifications(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Ok(notifications) = sql::get_notifications(user_id).await { + let json_array: Vec = notifications + .into_iter() + .map(|(sender, amount)| { + DataValue::Container(vec![ + (DataTypes::sender_id, DataValue::Number(sender)), + (DataTypes::amount, DataValue::Number(amount)), + ]) + }) + .collect(); + + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_data(DataTypes::notifications, DataValue::Array(json_array)); + self.send(&response).await + } else { + Ok(()) + } + } + + async fn handle_read_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::read_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_push_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::add_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_ping(&self, cv: CommunicationValue) -> OmikronResult<()> { if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - if let Ok(val) = last_ping.to_string().parse::() { - *self.ping.write().await = val; - } + *self.ping.write().await = *last_ping; } - let _ = self - .send(&CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id())) - .await; + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + self.send(&response).await } - pub async fn handle_close(&self) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; + // ------------------------------------------------------------------------- + // Utilities + // ------------------------------------------------------------------------- + async fn send(&self, cv: &CommunicationValue) -> OmikronResult<()> { + log_cv_out!(PrintType::Omikron, cv); + + let guard = self.sender.lock().await; + let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; + + sender + .send(cv) + .await + .map_err(|e| OmikronError::Send(e.to_string())) + } + + async fn send_error_response( + &self, + message_id: u32, + error_type: CommunicationType, + ) -> OmikronResult<()> { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send(&error).await + } + + pub async fn close(&self) {} + + async fn cleanup(&self) { + if let Some(omikron_id) = self.state.read().await.omikron_id() { if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - + log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } } + + // Cancel cleanup task + if let Some(handle) = self.cleanup_handle.lock().await.take() { + handle.abort(); + } + } + + fn arc_self(&self) -> Arc { + // This is a bit of a hack - in practice you'd store the Arc in the struct + // or use weak references. For now, we rely on the caller having the Arc. + panic!("Use the Arc directly") + } + + // Public API for external use + pub async fn is_authenticated(&self) -> bool { + self.state.read().await.is_authenticated() + } + + pub async fn get_omikron_id(&self) -> Option { + self.state.read().await.omikron_id() + } + + pub async fn send_message(&self, cv: &CommunicationValue) -> OmikronResult<()> { + self.send(cv).await } } + +// ============================================================================ +// Server Startup +// ============================================================================ + +pub async fn start(port: u16) -> Result<(), Box> { + let _ = aws_lc_rs::default_provider().install_default(); + + let tls_cfg = load_tls().expect("TLS config failed"); + let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg)?; + let server_cfg = ServerConfig::with_crypto(Arc::new(server_crypto)); + + let mut host: Host = epsilon_native::host(port, server_cfg).await?; + log!("OmikronServer listening on port {}", port); + + while let Some((sender, receiver)) = host.next().await { + tokio::spawn(async move { + let conn = OmikronConnection::new(sender); + conn.handle(receiver).await; + }); + } + + Ok(()) +} + +fn load_tls() -> Option { + let _ = aws_lc_rs::default_provider().install_default(); + + let mut cert_pem = load_file_buf("certs", "cert.pem").ok()?; + let cert_chain = rustls_pemfile::certs(&mut cert_pem) + .collect::, _>>() + .ok()?; + + let key_pem = load_file_vec("certs", "key.pem").ok()?; + let key_der = rustls_pemfile::private_key(&mut &*key_pem).ok()??; + + let cfg = CryptoConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key_der) + .ok()?; + + Some(cfg) +} diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index f481702..37bad20 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -8,7 +8,13 @@ pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); pub async fn add_omikron(conn: Arc) { - let id = conn.get_omikron_id().await; + let id = match conn.get_omikron_id().await { + Some(id) => id, + _ => { + conn.close().await; + return; + } + }; if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { old.close().await; From cf38693810ad2bb462003a468ebcf1c58407f570 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 3 Mar 2026 16:30:35 +0100 Subject: [PATCH 080/220] [WIP] QUIC connection --- Cargo.lock | 599 +------------ Cargo.toml | 43 +- src/main.rs | 6 +- src/server/api.rs | 22 +- src/server/omikron_connection.rs | 1370 +++++++++++++++++++++++------- src/server/omikron_manager.rs | 8 +- 6 files changed, 1123 insertions(+), 925 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40c314f..833d221 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,9 +14,7 @@ dependencies = [ "ansi_term", "anyhow", "async-trait", - "async-tungstenite", - "axum", - "base64 0.22.1", + "base64", "bytes", "color-eyre", "dashmap", @@ -28,9 +26,6 @@ dependencies = [ "hex", "hkdf", "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", "json", "once_cell", "pnet", @@ -38,14 +33,15 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "reqwest", - "rustls 0.23.37", - "rustls-pemfile 2.2.0", + "rustls", + "rustls-pemfile", "sha1", "sha2", "sqlx", "strum 0.27.2", "strum_macros 0.27.2", "sysinfo", + "thiserror 2.0.18", "tokio", "tokio-rustls", "tokio-tungstenite", @@ -111,7 +107,7 @@ dependencies = [ "actix-service", "actix-tls", "actix-utils", - "base64 0.22.1", + "base64", "bitflags 2.11.0", "brotli", "bytes", @@ -531,18 +527,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "async-native-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" -dependencies = [ - "futures-util", - "native-tls", - "thiserror 1.0.69", - "url", -] - [[package]] name = "async-std" version = "1.13.2" @@ -575,19 +559,6 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" -[[package]] -name = "async-tls" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e" -dependencies = [ - "futures-core", - "futures-io", - "rustls 0.21.12", - "rustls-pemfile 1.0.4", - "webpki-roots 0.22.6", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -599,36 +570,6 @@ dependencies = [ "syn", ] -[[package]] -name = "async-tungstenite" -version = "0.32.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc405d38be14342132609f06f02acaf825ddccfe76c4824a69281e0458ebd4" -dependencies = [ - "async-native-tls", - "async-std", - "async-tls", - "atomic-waker", - "futures-core", - "futures-io", - "futures-task", - "futures-util", - "gio", - "glib", - "log", - "native-tls", - "openssl", - "pin-project-lite", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-native-tls", - "tokio-openssl", - "tokio-rustls", - "tungstenite", - "webpki-roots 1.0.6", -] - [[package]] name = "atoi" version = "2.0.0" @@ -652,9 +593,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", "zeroize", @@ -662,9 +603,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.37.1" +version = "0.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" dependencies = [ "cc", "cmake", @@ -672,61 +613,6 @@ dependencies = [ "fs_extra", ] -[[package]] -name = "axum" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" -dependencies = [ - "axum-core", - "base64 0.22.1", - "bytes", - "form_urlencoded", - "futures-util", - "http 1.4.0", - "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", - "sha1", - "sync_wrapper", - "tokio", - "tokio-tungstenite", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http 1.4.0", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.76" @@ -742,12 +628,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -872,16 +752,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" -[[package]] -name = "cfg-expr" -version = "0.20.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cef5b5a1a6827c7322ae2a636368a573006b27cfa76c7ebd53e834daeaab6a" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.4" @@ -1247,9 +1117,10 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" dependencies = [ "byteorder", + "quinn", "rand 0.8.5", "strum 0.28.0", "strum_macros 0.28.0", @@ -1258,14 +1129,14 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#dc17684bc9308c79559e0d4648c6e730cc567c89" +source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" dependencies = [ "anyhow", "async-trait", "bytes", "epsilon-core", "quinn", - "rustls 0.23.37", + "rustls", "thiserror 2.0.18", "tokio", ] @@ -1407,21 +1278,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" -[[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" @@ -1630,80 +1486,6 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -[[package]] -name = "gio" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5ff48bf600c68b476e61dc6b7c762f2f4eb91deef66583ba8bb815c30b5811a" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", -] - -[[package]] -name = "gio-sys" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0071fe88dba8e40086c8ff9bbb62622999f49628344b1d1bf490a48a29d80f22" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.61.2", -] - -[[package]] -name = "glib" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16de123c2e6c90ce3b573b7330de19be649080ec612033d397d72da265f1bd8b" -dependencies = [ - "bitflags 2.11.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", -] - -[[package]] -name = "glib-macros" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf59b675301228a696fe01c3073974643365080a76cc3ed5bc2cbc466ad87f17" -dependencies = [ - "heck", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "glib-sys" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d95e1a3a19ae464a7286e14af9a90683c64d70c02532d88d87ce95056af3e6c" -dependencies = [ - "libc", - "system-deps", -] - [[package]] name = "glob" version = "0.3.3" @@ -1722,17 +1504,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gobject-sys" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dca35da0d19a18f4575f3cb99fe1c9e029a2941af5662f326f738a21edaf294" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "h2" version = "0.3.27" @@ -1924,7 +1695,6 @@ dependencies = [ "http 1.4.0", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -1942,9 +1712,7 @@ dependencies = [ "http 1.4.0", "hyper", "hyper-util", - "log", - "rustls 0.23.37", - "rustls-native-certs", + "rustls", "rustls-pki-types", "tokio", "tokio-rustls", @@ -1957,7 +1725,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-channel", "futures-util", @@ -1971,7 +1739,6 @@ dependencies = [ "socket2 0.6.2", "system-configuration", "tokio", - "tower-layer", "tower-service", "tracing", "windows-registry", @@ -2140,9 +1907,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.11.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "ipnetwork" @@ -2267,12 +2034,13 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" dependencies = [ "bitflags 2.11.0", "libc", + "plain", "redox_syscall 0.7.3", ] @@ -2355,12 +2123,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - [[package]] name = "md-5" version = "0.10.6" @@ -2405,23 +2167,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "no-std-net" version = "0.6.0" @@ -2529,50 +2274,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" -dependencies = [ - "bitflags 2.11.0", - "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.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "openssl-sys" -version = "0.9.111" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "owo-colors" version = "4.3.0" @@ -2647,9 +2354,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", "fastrand 2.3.0", @@ -2683,6 +2390,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "pnet" version = "0.35.0" @@ -2856,15 +2569,6 @@ dependencies = [ "syn", ] -[[package]] -name = "proc-macro-crate" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" version = "1.0.106" @@ -2886,7 +2590,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.37", + "rustls", "socket2 0.6.2", "thiserror 2.0.18", "tokio", @@ -2908,7 +2612,7 @@ dependencies = [ "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "slab", @@ -3071,7 +2775,7 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "encoding_rs", "futures-core", @@ -3088,7 +2792,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.37", + "rustls", "rustls-pki-types", "rustls-platform-verifier", "sync_wrapper", @@ -3185,18 +2889,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustls" -version = "0.21.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" -dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", -] - [[package]] name = "rustls" version = "0.23.37" @@ -3208,7 +2900,7 @@ dependencies = [ "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.9", + "rustls-webpki", "subtle", "zeroize", ] @@ -3225,15 +2917,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -3264,10 +2947,10 @@ dependencies = [ "jni", "log", "once_cell", - "rustls 0.23.37", + "rustls", "rustls-native-certs", "rustls-platform-verifier-android", - "rustls-webpki 0.103.9", + "rustls-webpki", "security-framework", "security-framework-sys", "webpki-root-certs", @@ -3280,16 +2963,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "rustls-webpki" version = "0.103.9" @@ -3338,16 +3011,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "security-framework" version = "3.7.0" @@ -3420,26 +3083,6 @@ dependencies = [ "zmij", ] -[[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_spanned" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" -dependencies = [ - "serde_core", -] - [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3606,7 +3249,7 @@ checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ "async-io 1.13.0", "async-std", - "base64 0.22.1", + "base64", "bytes", "crc", "crossbeam-queue", @@ -3677,7 +3320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bitflags 2.11.0", "byteorder", "bytes", @@ -3719,7 +3362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64 0.22.1", + "base64", "bitflags 2.11.0", "byteorder", "crc", @@ -3898,38 +3541,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "7.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c8f33736f986f16d69b6cb8b03f55ddcad5c41acc4ccc39dd88e84aa805e7f" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" - -[[package]] -name = "tempfile" -version = "3.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" -dependencies = [ - "fastrand 2.3.0", - "getrandom 0.4.1", - "once_cell", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - [[package]] name = "thiserror" version = "1.0.69" @@ -4037,9 +3648,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.49.0" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ "bytes", "libc", @@ -4054,43 +3665,22 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" 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-openssl" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59df6849caa43bb7567f9a36f863c447d95a11d5903c9cc334ba32576a27eadd" -dependencies = [ - "openssl", - "openssl-sys", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.37", + "rustls", "tokio", ] @@ -4123,57 +3713,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.9.12+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime", - "toml_parser", - "toml_writer", - "winnow", -] - -[[package]] -name = "toml_datetime" -version = "0.7.5+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.23.10+spec-1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_parser", - "winnow", -] - -[[package]] -name = "toml_parser" -version = "1.0.9+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" -dependencies = [ - "winnow", -] - -[[package]] -name = "toml_writer" -version = "1.0.6+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" - [[package]] name = "tower" version = "0.5.3" @@ -4187,7 +3726,6 @@ dependencies = [ "tokio", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -4291,13 +3829,9 @@ dependencies = [ "http 1.4.0", "httparse", "log", - "native-tls", "rand 0.9.2", - "rustls 0.23.37", - "rustls-pki-types", "sha1", "thiserror 2.0.18", - "url", "utf-8", ] @@ -4415,12 +3949,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version-compare" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" - [[package]] name = "version_check" version = "0.9.5" @@ -4595,16 +4123,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring", - "untrusted", -] - [[package]] name = "webpki-root-certs" version = "1.0.6" @@ -4614,24 +4132,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - -[[package]] -name = "webpki-roots" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "whoami" version = "1.6.1" @@ -5107,15 +4607,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "winnow" -version = "0.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" -dependencies = [ - "memchr", -] - [[package]] name = "wit-bindgen" version = "0.51.0" @@ -5367,9 +4858,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c745c48e1007337ed136dc99df34128b9faa6ed542d80a1c673cf55a6d7236c8" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 8432d26..a5cb77f 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,39 +15,6 @@ aes-gcm = "*" ansi_term = "0.12.1" anyhow = "1.0.101" async-trait = "0.1.89" -async-tungstenite = { version = "0.32.0", features = [ - "futures-03-sink", - "futures-util", - "handshake", - "__rustls-tls", - "async-native-tls", - "async-std", - "async-std-runtime", - "async-tls", - "gio", - "gio-runtime", - "glib", - "openssl", - "real-async-native-tls", - "real-async-tls", - "real-native-tls", - "real-tokio-native-tls", - "real-tokio-openssl", - "real-tokio-rustls", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-native-tls", - "tokio-openssl", - "tokio-runtime", - "tokio-rustls-manual-roots", - "tokio-rustls-native-certs", - "tokio-rustls-webpki-roots", - "url", - "verbose-logging", - "webpki-roots", -] } -axum = { version = "0.8.8", features = ["ws", "http2"] } base64 = "0.22.1" bytes = "1.11.1" color-eyre = "0.6.5" @@ -58,16 +25,14 @@ futures-util = "0.3.31" hex = "0.4.3" hkdf = "0.12.4" http-body-util = "0.1.3" -hyper = { version = "1.8.1", features = ["http2", "full"] } -hyper-rustls = { version = "0.27.7", features = ["http2"] } -hyper-util = { version = "0.1.20", features = ["full"] } json = "0.12.4" once_cell = "1.21.3" pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = "0.13.2" -rustls = "0.23.37" +reqwest = { version = "0.13.2" } +rustls = { version = "0.23.37", default-features = false, features = ["std", "tls12", "aws-lc-rs", "prefer-post-quantum"] } +quinn = { version = "0.11.9", default-features = false, features = ["runtime-tokio", "rustls-aws-lc-rs"] } rustls-pemfile = "2.2.0" sha1 = "0.10.6" sha2 = "0.10.9" @@ -85,4 +50,4 @@ uuid = { version = "1.19.0", features = ["v4"] } walkdir = "2.5.0" x448 = "0.6.0" zip = "6.0.0" -quinn = "0.11.9" +thiserror = "2.0.18" diff --git a/src/main.rs b/src/main.rs index 043700c..72c42c5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,9 @@ async fn main() { startup(); log_in!("Incoming messages"); log_out!("Outgoing messages"); + + let _ = omikron_connection::start(9187).await; + log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { @@ -43,7 +46,8 @@ async fn main() { } else { log!(" Users"); } + let _ = server::server::start(9188).await; - let _ = omikron_connection::OmikronServer::start(9187).await; + tokio::signal::ctrl_c().await.unwrap(); } diff --git a/src/server/api.rs b/src/server/api.rs index 7f3c728..b216112 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -65,16 +65,20 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { // ================================================== ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { - let id = omikron_conn.get_omikron_id().await; + if let Some(id) = omikron_conn.get_omikron_id().await { + if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); - if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - - (StatusCode::OK, res.dump()) + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } } else { let mut res = JsonValue::new_object(); res["status"] = "error".into(); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index ade9746..b509787 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,412 +1,1140 @@ -use crate::server::{omikron_manager, short_link::add_short_link}; -use crate::sql::{sql, sql::get_omikron_by_id, user_online_tracker}; -use crate::util::file_util::load_file_buf; -use crate::util::{crypto_helper::encrypt, logger::PrintType}; -use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in}; +use crate::{ + get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, + server::{omikron_manager, short_link::add_short_link}, + sql::{ + connection_status::UserStatus, + sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, + user_online_tracker::{self}, + }, + util::{ + crypto_helper::encrypt, + file_util::{load_file_buf, load_file_vec}, + logger::PrintType, + }, +}; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use epsilon_native::{Receiver, Sender, host}; +use epsilon_native::{Host, Receiver, Sender}; use quinn::ServerConfig; use rand::{Rng, distributions::Alphanumeric}; -use rustls::pki_types::PrivateKeyDer; -use std::sync::Arc; -use tokio::sync::RwLock; +use rustls::{ServerConfig as CryptoConfig, crypto::aws_lc_rs}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::{ + sync::{Mutex, RwLock}, + time::interval, +}; use x448::PublicKey; -pub struct OmikronServer; +// ============================================================================ +// Configuration +// ============================================================================ -impl OmikronServer { - pub async fn start(port: u16) -> Result<(), Box> { - let tls_cfg = OmikronServer::load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg) - .expect("Failed to convert to QuicServerConfig"); - let mut host = host(port, ServerConfig::with_crypto(Arc::new(server_crypto))).await?; - tokio::spawn(async move { - while let Some((sender, receiver)) = host.next().await { - let connection = OmikronConnection::new(sender); - tokio::spawn(Self::connection_loop(connection, receiver)); +const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); +const MAX_WAITING_AGE: Duration = Duration::from_secs(60); + +// ============================================================================ +// Error Types +// ============================================================================ + +#[derive(Debug, thiserror::Error)] +pub enum OmikronError { + #[error("Not connected")] + NotConnected, + #[error("Not authenticated")] + NotAuthenticated, + #[error("Invalid response")] + InvalidResponse, + #[error("Authentication failed")] + AuthenticationFailed, + #[error("SQL error: {0}")] + Sql(String), + #[error("Send error: {0}")] + Send(String), +} + +pub type OmikronResult = Result; + +// ============================================================================ +// Waiting Task System (Preserved from original) +// ============================================================================ + +pub struct WaitingTask { + pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub inserted_at: Instant, +} + +// ============================================================================ +// Connection State +// ============================================================================ + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum AuthState { + Unauthenticated, + Identified { omikron_id: i64 }, + Authenticated { omikron_id: i64 }, +} + +impl AuthState { + fn is_authenticated(&self) -> bool { + match self { + AuthState::Authenticated { omikron_id } => true, + _ => false, + } + } + + fn omikron_id(&self) -> Option { + match self { + AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { + Some(*omikron_id) } - }); - - Ok(()) - } - fn load_tls() -> Option { - let mut cert_file_buf = load_file_buf("certs", "cert.pem").ok()?; - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; - - let cert_chain = rustls_pemfile::certs(&mut cert_file_buf) - .collect::, _>>() - .ok()?; - - let mut keys: Vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; - - if keys.is_empty() { - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; - keys = rustls_pemfile::rsa_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; + _ => None, } - - if keys.is_empty() { - return None; - } - - let cfg = rustls::ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, keys.remove(0)) - .ok()?; - - Some(cfg) - } - - async fn connection_loop(conn: Arc, receiver: Receiver) { - while let Ok(cv) = receiver.receive().await { - log_cv_in!(PrintType::Omikron, cv); - conn.clone().handle_message(cv).await; - } - - conn.handle_close().await; } } +// ============================================================================ +// Omikron Connection (Epsilon/QUIC-based) +// ============================================================================ + pub struct OmikronConnection { - sender: Arc>>, - omikron_id: Arc>, - pub_key: Arc>>>, + id: u64, + sender: Mutex>, + state: RwLock, - identified: Arc>, - challenged: Arc>, - challenge: Arc>, + // Authentication state (preserved from original) + challenge: RwLock, + pub_key: RwLock>>, - ping: Arc>, + // Ping tracking + pub ping: RwLock, - waiting_tasks: - DashMap, CommunicationValue) -> bool + Send + Sync>>, + // Waiting tasks for request/response pattern + waiting_tasks: DashMap, + + // Cleanup handle + cleanup_handle: Mutex>>, } impl OmikronConnection { + // ------------------------------------------------------------------------- + // Construction + // ------------------------------------------------------------------------- + pub fn new(sender: Sender) -> Arc { - Arc::new(Self { - sender: Arc::new(RwLock::new(Some(sender))), - omikron_id: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - identified: Arc::new(RwLock::new(false)), - challenged: Arc::new(RwLock::new(false)), - challenge: Arc::new(RwLock::new(String::new())), - ping: Arc::new(RwLock::new(-1)), + let conn = Arc::new(Self { + id: rand::random(), + sender: Mutex::new(Some(sender)), + state: RwLock::new(AuthState::Unauthenticated), + challenge: RwLock::new(String::new()), + pub_key: RwLock::new(None), + ping: RwLock::new(-1), waiting_tasks: DashMap::new(), - }) + cleanup_handle: Mutex::new(None), + }); + + // Start cleanup task for waiting tasks + let cleanup_conn = conn.clone(); + let handle = tokio::spawn(async move { + let mut ticker = interval(CLEANUP_INTERVAL); + loop { + ticker.tick().await; + cleanup_conn + .waiting_tasks + .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + } + }); + + // Store handle (would need block_in_place or similar to set this immediately) + // For now, we'll handle this differently in handle() + + conn } - async fn send(&self, cv: &CommunicationValue) { - log_cv_out!(PrintType::Omikron, cv); + // ------------------------------------------------------------------------- + // Main Handler Loop + // ------------------------------------------------------------------------- - if let Some(sender) = self.sender.read().await.as_ref() { - if sender.send(cv).await.is_err() { - self.handle_close().await; + pub async fn handle(self: Arc, mut receiver: Receiver) { + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection started" + ); + + // Start cleanup task + let cleanup_conn = self.clone(); + let _cleanup_handle = tokio::spawn(async move { + let mut ticker = interval(CLEANUP_INTERVAL); + loop { + ticker.tick().await; + cleanup_conn + .waiting_tasks + .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + } + }); + + while let Ok(cv) = receiver.receive().await { + if let Err(e) = self.clone().process_message(cv).await { + log_err!(0, PrintType::Omega, "Error processing message: {}", e); + // Don't break on error unless critical - match original WebSocket behavior + if matches!(e, OmikronError::NotConnected) { + break; + } + } + } + + // Connection closed + self.cleanup().await; + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection closed" + ); + } + + // ------------------------------------------------------------------------- + // Message Processing + // ------------------------------------------------------------------------- + + async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + // Log incoming + log_cv_in!(PrintType::Omikron, &cv); + + let msg_id = cv.get_id(); + + // Check waiting tasks first (response to previous request) + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + let _ = (task.task)(self.clone(), cv); + return Ok(()); + } + + // Handle ping regardless of auth state + if cv.is_type(CommunicationType::ping) { + return self.handle_ping(cv).await; + } + + // Route based on authentication state + match *self.state.read().await { + AuthState::Unauthenticated => self.handle_unauthenticated(cv).await, + AuthState::Identified { .. } => self.handle_identified(cv).await, + AuthState::Authenticated { omikron_id } => { + self.handle_authenticated(cv, omikron_id).await } } } - async fn send_error_response(&self, id: u32, comm_type: CommunicationType) { - let response = CommunicationValue::new(comm_type).with_id(id); - self.send(&response).await; - } + // ------------------------------------------------------------------------- + // Authentication Handlers + // ------------------------------------------------------------------------- - pub async fn get_omikron_id(&self) -> i64 { - *self.omikron_id.read().await - } - - async fn is_identified(&self) -> bool { - *self.identified.read().await && *self.challenged.read().await - } - - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; + async fn handle_unauthenticated(&self, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::identification) { + self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); } - if let Some((_, task)) = self.waiting_tasks.remove(&cv.get_id()) { - let _ = task(self.clone(), cv.clone()); - return; - } + // Extract omikron ID + let omikron_id = cv + .get_data(DataTypes::omikron) + .as_number() + .ok_or(OmikronError::InvalidResponse)?; - if !self.is_identified().await { - self.handle_identification(cv).await; - return; - } + // Lookup omikron in database + let (public_key, _) = get_omikron_by_id(omikron_id) + .await + .map_err(|e| OmikronError::Sql(e.to_string()))?; - self.handle_authenticated(cv).await; + let pub_key_bytes = STANDARD + .decode(&public_key) + .map_err(|_| OmikronError::AuthenticationFailed)?; + + let omikron_pub_key = + PublicKey::from_bytes(&pub_key_bytes).ok_or(OmikronError::AuthenticationFailed)?; + + // Generate challenge + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + // Store state + *self.challenge.write().await = challenge.clone(); + *self.pub_key.write().await = Some(pub_key_bytes); + *self.state.write().await = AuthState::Identified { omikron_id }; + + // Encrypt challenge + let encrypted = encrypt(get_private_key(), omikron_pub_key, &challenge) + .map_err(|_| OmikronError::AuthenticationFailed)?; + + // Send challenge response + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + + self.send(&response).await } - async fn handle_identification(self: &Arc, cv: CommunicationValue) { - let identified = *self.identified.read().await; - let challenged = *self.challenged.read().await; + async fn handle_identified(&self, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::challenge_response) { + self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); + } - if !identified && cv.is_type(CommunicationType::identification) { - let omikron_id = cv.get_data(DataTypes::omikron).as_number().unwrap_or(0); + let client_response = cv + .get_data(DataTypes::challenge) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; - let (public_key, _) = match get_omikron_by_id(omikron_id).await { - Ok(v) => v, - Err(_) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_not_authenticated) - .with_id(cv.get_id()), - ) - .await; - return; - } - }; + let expected_challenge = self.challenge.read().await.clone(); - let pub_key_bytes = match STANDARD.decode(&public_key) { - Ok(b) => b, - Err(_) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_invalid_omikron_id) - .with_id(cv.get_id()), - ) - .await; - return; - } - }; + if client_response == expected_challenge { + // Challenge passed - mark as authenticated + let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); + *self.state.write().await = AuthState::Authenticated { omikron_id }; - let omikron_pub_key = match PublicKey::from_bytes(&pub_key_bytes) { - Some(k) => k, - _ => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_invalid_public_key) - .with_id(cv.get_id()), - ) - .await; - return; - } - }; + // Register with manager + omikron_manager::add_omikron(self.arc_self()).await; - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.omikron_id.write().await = omikron_id; - *self.challenge.write().await = challenge.clone(); - *self.pub_key.write().await = Some(pub_key_bytes); - *self.identified.write().await = true; - - let encrypted = - encrypt(get_private_key(), omikron_pub_key, &challenge).unwrap_or_default(); - - let response = CommunicationValue::new(CommunicationType::challenge) + // Send success response + let response = CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + .add_data(DataTypes::accepted, DataValue::Bool(true)); + + self.send(&response).await?; + log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); + Ok(()) + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + .await; + Err(OmikronError::AuthenticationFailed) + } + } + + // ------------------------------------------------------------------------- + // Authenticated Message Handlers + // ------------------------------------------------------------------------- + + async fn handle_authenticated( + &self, + cv: CommunicationValue, + omikron_id: i64, + ) -> OmikronResult<()> { + match cv.get_type() { + // Link shortening + CommunicationType::shorten_link => self.handle_shorten_link(cv).await, + + // Online status tracking + CommunicationType::user_connected => { + self.handle_user_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::user_disconnected => { + self.handle_user_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_connected => { + self.handle_iota_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_disconnected => { + self.handle_iota_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::sync_client_iota_status => { + self.handle_sync_status(cv, omikron_id).await; + Ok(()) + } + + // Data queries + CommunicationType::get_user_data => self.handle_get_user_data(cv).await, + CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, + + // Registration + CommunicationType::get_register => self.handle_get_register(cv).await, + CommunicationType::complete_register_iota => { + self.handle_complete_register_iota(cv).await + } + CommunicationType::complete_register_user => { + self.handle_complete_register_user(cv).await + } + + // Data modification + CommunicationType::change_user_data => self.handle_change_user_data(cv).await, + CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, + CommunicationType::delete_user => self.handle_delete_user(cv).await, + CommunicationType::delete_iota => self.handle_delete_iota(cv).await, + + // Notifications + CommunicationType::get_notifications => self.handle_get_notifications(cv).await, + CommunicationType::read_notification => self.handle_read_notification(cv).await, + CommunicationType::push_notification => self.handle_push_notification(cv).await, + + _ => { + log_err!( + 0, + PrintType::Omega, + "Unknown message type: {:?}", + cv.get_type() + ); + Ok(()) + } + } + } + + // ------------------------------------------------------------------------- + // Specific Handlers (ported from original WebSocket implementation) + // ------------------------------------------------------------------------- + + async fn handle_shorten_link(&self, cv: CommunicationValue) -> OmikronResult<()> { + let link = cv + .get_data(DataTypes::link) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; + + let short = add_short_link(link) + .await + .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; + + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, DataValue::Str(short)); + + self.send(&response).await + } + + async fn handle_user_connected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "User connected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_online, + omikron_id, + ); + } + } + + async fn handle_user_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "User disconnected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_offline, + status.omikron_id, + ); + } + } + } + + async fn handle_iota_connected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA connected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + + let mut user_ids = Vec::new(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { + user_ids.push(DataValue::Number(user_id)); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_offline, + omikron_id, + ); + } + } else { + log_in!(PrintType::General, "SQL error loading users for IOTA"); + } + + let response = CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); let _ = self.send(&response).await; - return; + } else { + log_in!(PrintType::General, "No IOTA ID found"); } + } - if identified && !challenged && cv.is_type(CommunicationType::challenge_response) { - let client_response = cv.get_data(DataTypes::challenge).as_str().unwrap_or(""); - - if client_response == *self.challenge.read().await { - *self.challenged.write().await = true; - - omikron_manager::add_omikron(self.clone()).await; - - let _ = self - .send( - &CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::BoolTrue), - ) - .await; - - log_in!(PrintType::Omega, "Omikron Connected"); - } else { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error_invalid_challenge) - .with_id(cv.get_id()), - ) - .await; + async fn handle_iota_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA disconnected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); + } } } } - async fn handle_authenticated(self: &Arc, cv: CommunicationValue) { - if cv.is_type(CommunicationType::shorten_link) { - if let Some(link) = cv.get_data(DataTypes::link).as_str() { - if let Ok(short) = add_short_link(link).await { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, DataValue::Str(short)), - ) - .await; + async fn handle_sync_status(&self, cv: CommunicationValue, omikron_id: i64) { + if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { + for user_id_val in user_ids { + if let DataValue::Number(user_id) = user_id_val { + user_online_tracker::track_user_status( + *user_id, + UserStatus::user_online, + omikron_id, + ); } } - return; } - if cv.is_type(CommunicationType::get_register) { - let register_id = sql::get_register_id().await; - let _ = self - .send( - &CommunicationValue::new(CommunicationType::get_register) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)), - ) - .await; - return; - } - - if cv.is_type(CommunicationType::delete_user) { - let user_id = cv.get_sender(); - match sql::delete_user(user_id as i64).await { - Ok(_) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()), - ) - .await; - } - Err(e) => { - let _ = self - .send( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), - ) - .await; + if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { + for iota_id_val in iota_ids { + if let DataValue::Number(iota_id) = iota_id_val { + user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); } } - return; + } + } + + async fn handle_get_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + // Try by user_id first + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok(user_data) = get_by_user_id(user_id as i64).await { + let response = self.build_user_data_response(cv.get_id(), user_data).await; + return self.send(&response).await; + } } - if cv.is_type(CommunicationType::delete_iota) { - if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { - match sql::delete_iota(*iota_id).await { + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok(user_data) = get_by_username(username).await { + let response = self.build_user_data_response(cv.get_id(), user_data).await; + return self.send(&response).await; + } + } + + // Not found + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_user_data_response( + &self, + msg_id: u32, + user: ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + ) -> CommunicationValue { + let ( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + ) = user; + + let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(msg_id) + .add_data(DataTypes::username, DataValue::Str(username.clone())) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::user_id, DataValue::Number(id)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) + .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) + .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); + + // Display name (fallback to username) + let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); + response = response.add_data(DataTypes::display, DataValue::Str(display_name)); + + // Optional fields + if let Some(s) = status.filter(|s| !s.is_empty()) { + response = response.add_data(DataTypes::status, DataValue::Str(s)); + } + if let Some(a) = about.filter(|a| !a.is_empty()) { + response = response.add_data(DataTypes::about, DataValue::Str(a)); + } + if let Some(av) = avatar { + response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); + } + + // Online status + let user_status = user_online_tracker::get_user_status(id); + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + if let Some(us) = user_status { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(us.connection_type.to_string()), + ); + response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); + } else { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(UserStatus::iota_offline.to_string()), + ); + } + + response = response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ); + + response + } + + async fn handle_get_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + // Try by iota_id + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; + } + } + + // Try by user_id + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id as i64).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id as i64), + None, + ) + .await; + return self.send(&response).await; + } + } + } + + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_username(username).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id), + Some(username.to_string()), + ) + .await; + return self.send(&response).await; + } + } + } + + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_iota_data_response( + &self, + msg_id: u32, + iota_id: i64, + public_key: String, + user_id: Option, + username: Option, + ) -> CommunicationValue { + let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(msg_id) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + + if let Some(uid) = user_id { + response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); + } + if let Some(uname) = username { + response = response.add_data(DataTypes::username, DataValue::Str(uname)); + } + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ) + } + + async fn handle_get_register(&self, cv: CommunicationValue) -> OmikronResult<()> { + let register_id = sql::get_register_id().await; + let response = CommunicationValue::new(CommunicationType::get_register) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); + self.send(&response).await + } + + async fn handle_complete_register_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + let iota_id_opt = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + + if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { + if let Some(iota_id) = iota_id_opt { + // Register existing IOTA + match sql::register_complete_iota(iota_id, public_key.to_string()).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success) .with_id(cv.get_id()); - self.send(&response).await; + self.send(&response).await } Err(e) => { - self.send( - &CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())), - ) - .await; + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await; + // Create new IOTA + match sql::create_new_iota(public_key.to_string()).await { + Ok(new_iota_id) => { + let response = + CommunicationValue::new(CommunicationType::complete_register_iota) + .with_id(cv.get_id()) + .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_complete_register_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv + .get_data(DataTypes::user_id) + .as_number() + .map(|n| n as i64); + let username = cv + .get_data(DataTypes::username) + .as_str() + .map(|s| s.to_string()); + let public_key = cv + .get_data(DataTypes::public_key) + .as_str() + .map(|s| s.to_string()); + let iota_id = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + let reset_token = cv + .get_data(DataTypes::reset_token) + .as_str() + .map(|s| s.to_string()); + + if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = + (user_id, username, public_key, iota_id, reset_token) + { + match sql::register_complete_user(uid, uname, pk, iid, rt).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_change_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + let mut success = true; + let mut error_message = String::new(); + + // Process each field + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(display) = cv.get_data(DataTypes::display).as_str() { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(about) = cv.get_data(DataTypes::about).as_str() { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(status) = cv.get_data(DataTypes::status).as_str() { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let (Some(public_key), Some(private_key_hash)) = ( + cv.get_data(DataTypes::public_key).as_str(), + cv.get_data(DataTypes::private_key_hash).as_str(), + ) { + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), + ) + .await + { + success = false; + error_message = e.to_string(); } - return; } - // NOTIFICATIONS - if cv.is_type(CommunicationType::get_notifications) { - let user_id = cv.get_sender(); - if let Ok(notifications) = sql::get_notifications(user_id as i64).await { - let mut json_array = Vec::new(); - for (sender, amount) in notifications { - let mut obj = Vec::new(); - let _ = obj.push((DataTypes::sender_id, DataValue::Number(sender))); - let _ = obj.push((DataTypes::amount, DataValue::Number(amount))); - json_array.push(DataValue::Container(obj)); + if success { + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } + + async fn handle_change_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + + if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64), + cv.get_data(DataTypes::reset_token).as_str(), + cv.get_data(DataTypes::new_token).as_str(), + ) { + match sql::get_by_user_id(user_id).await { + Ok(user) => { + let current_token = user.11; // reset_token field + if current_token == reset_token { + let mut success = true; + let mut error_message = String::new(); + + if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + success = false; + error_message = e.to_string(); + } + if success { + if let Err(e) = sql::change_token(user_id, new_token.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } else { + self.send_error_response( + cv.get_id(), + CommunicationType::error_invalid_challenge, + ) + .await + } } - let response = CommunicationValue::new(CommunicationType::get_notifications) + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error_not_found) + .await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_delete_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + match sql::delete_user(user_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(json_array)); - self.send(&response).await; - } - } - if cv.is_type(CommunicationType::read_notification) { - if let (user_id, Some(other_id)) = ( - cv.get_sender(), - cv.get_data(DataTypes::sender_id).as_number(), - ) { - if let Ok(_) = sql::read_notification(user_id as i64, other_id).await { - let response = CommunicationValue::new(CommunicationType::read_notification) - .with_id(cv.get_id()); - self.send(&response).await; - } - } - } - if cv.is_type(CommunicationType::push_notification) { - if let (user_id, Some(other_id)) = ( - cv.get_sender(), - cv.get_data(DataTypes::sender_id).as_number(), - ) { - if let Ok(_) = sql::add_notification(user_id as i64, other_id).await { - let response = CommunicationValue::new(CommunicationType::push_notification) - .with_id(cv.get_id()); - self.send(&response).await; - } + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await } } } - 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; - } - pub async fn close(&self) { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - pub async fn handle_close(self: Arc) { - let omikron_id = self.get_omikron_id().await; - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron Disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; + async fn handle_delete_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + if let Some(iota_id) = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64) + { + match sql::delete_iota(iota_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await } } - async fn handle_ping(&self, cv: CommunicationValue) { + async fn handle_get_notifications(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Ok(notifications) = sql::get_notifications(user_id).await { + let json_array: Vec = notifications + .into_iter() + .map(|(sender, amount)| { + DataValue::Container(vec![ + (DataTypes::sender_id, DataValue::Number(sender)), + (DataTypes::amount, DataValue::Number(amount)), + ]) + }) + .collect(); + + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_data(DataTypes::notifications, DataValue::Array(json_array)); + self.send(&response).await + } else { + Ok(()) + } + } + + async fn handle_read_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::read_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_push_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::add_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_ping(&self, cv: CommunicationValue) -> OmikronResult<()> { if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - if let Ok(val) = last_ping.to_string().parse::() { - *self.ping.write().await = val; - } + *self.ping.write().await = *last_ping; } - let _ = self - .send(&CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id())) - .await; + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + self.send(&response).await } - pub async fn handle_close(&self) { - if self.is_identified().await { - let omikron_id = self.get_omikron_id().await; + // ------------------------------------------------------------------------- + // Utilities + // ------------------------------------------------------------------------- + async fn send(&self, cv: &CommunicationValue) -> OmikronResult<()> { + log_cv_out!(PrintType::Omikron, cv); + + let guard = self.sender.lock().await; + let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; + + sender + .send(cv) + .await + .map_err(|e| OmikronError::Send(e.to_string())) + } + + async fn send_error_response( + &self, + message_id: u32, + error_type: CommunicationType, + ) -> OmikronResult<()> { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send(&error).await + } + + pub async fn close(&self) {} + + async fn cleanup(&self) { + if let Some(omikron_id) = self.state.read().await.omikron_id() { if omikron_id != 0 { - log_in!(PrintType::Omega, "Omikron Disconnected"); - + log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); omikron_manager::remove_omikron(omikron_id).await; user_online_tracker::untrack_omikron(omikron_id).await; } } + + // Cancel cleanup task + if let Some(handle) = self.cleanup_handle.lock().await.take() { + handle.abort(); + } + } + + fn arc_self(&self) -> Arc { + // This is a bit of a hack - in practice you'd store the Arc in the struct + // or use weak references. For now, we rely on the caller having the Arc. + panic!("Use the Arc directly") + } + + // Public API for external use + pub async fn is_authenticated(&self) -> bool { + self.state.read().await.is_authenticated() + } + + pub async fn get_omikron_id(&self) -> Option { + self.state.read().await.omikron_id() + } + + pub async fn send_message(&self, cv: &CommunicationValue) -> OmikronResult<()> { + self.send(cv).await } } + +// ============================================================================ +// Server Startup +// ============================================================================ + +pub async fn start(port: u16) -> Result<(), Box> { + let _ = aws_lc_rs::default_provider().install_default(); + + let tls_cfg = load_tls().expect("TLS config failed"); + let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg)?; + let server_cfg = ServerConfig::with_crypto(Arc::new(server_crypto)); + + let mut host: Host = epsilon_native::host(port, server_cfg).await?; + log!("OmikronServer listening on port {}", port); + + while let Some((sender, receiver)) = host.next().await { + tokio::spawn(async move { + let conn = OmikronConnection::new(sender); + conn.handle(receiver).await; + }); + } + + Ok(()) +} + +fn load_tls() -> Option { + let _ = aws_lc_rs::default_provider().install_default(); + + let mut cert_pem = load_file_buf("certs", "cert.pem").ok()?; + let cert_chain = rustls_pemfile::certs(&mut cert_pem) + .collect::, _>>() + .ok()?; + + let key_pem = load_file_vec("certs", "key.pem").ok()?; + let key_der = rustls_pemfile::private_key(&mut &*key_pem).ok()??; + + let cfg = CryptoConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key_der) + .ok()?; + + Some(cfg) +} diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index f481702..37bad20 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -8,7 +8,13 @@ pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); pub async fn add_omikron(conn: Arc) { - let id = conn.get_omikron_id().await; + let id = match conn.get_omikron_id().await { + Some(id) => id, + _ => { + conn.close().await; + return; + } + }; if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { old.close().await; From b8133d729358d8177fa6115781ecdc4258ba4bb7 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:16:52 +0100 Subject: [PATCH 081/220] IDk --- src/server/omikron_connection.rs | 37 +++++++++----------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 0ae49b7..24b1754 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,6 +1,6 @@ use crate::server::{omikron_manager, short_link::add_short_link}; use crate::sql::{sql, sql::get_omikron_by_id, user_online_tracker}; -use crate::util::file_util::load_file_buf; +use crate::util::file_util::load_file_vec; use crate::util::{crypto_helper::encrypt, logger::PrintType}; use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in}; use base64::{Engine as _, engine::general_purpose::STANDARD}; @@ -8,8 +8,9 @@ use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; use epsilon_native::{Receiver, Sender, host}; use quinn::ServerConfig; +use quinn::crypto::rustls::QuicServerConfig; use rand::{Rng, distributions::Alphanumeric}; -use rustls::pki_types::PrivateKeyDer; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use std::sync::Arc; use tokio::sync::RwLock; use x448::PublicKey; @@ -19,8 +20,8 @@ pub struct OmikronServer; impl OmikronServer { pub async fn start(port: u16) -> Result<(), Box> { let tls_cfg = OmikronServer::load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg) - .expect("Failed to convert to QuicServerConfig"); + let server_crypto = + QuicServerConfig::try_from(tls_cfg).expect("Failed to convert to QuicServerConfig"); let mut host = host(port, ServerConfig::with_crypto(Arc::new(server_crypto))).await?; tokio::spawn(async move { while let Some((sender, receiver)) = host.next().await { @@ -32,33 +33,15 @@ impl OmikronServer { Ok(()) } fn load_tls() -> Option { - let mut cert_file_buf = load_file_buf("certs", "cert.pem").ok()?; - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; + let cert_bytes = load_file_vec("certs", "cert.der").ok()?; + let key_bytes = load_file_vec("certs", "key.der").ok()?; - let cert_chain = rustls_pemfile::certs(&mut cert_file_buf) - .collect::, _>>() - .ok()?; - - let mut keys: Vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; - - if keys.is_empty() { - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; - keys = rustls_pemfile::rsa_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; - } - - if keys.is_empty() { - return None; - } + let cert_chain = vec![CertificateDer::from(cert_bytes)]; + let private_key = PrivateKeyDer::try_from(key_bytes).ok()?; let cfg = rustls::ServerConfig::builder() .with_no_client_auth() - .with_single_cert(cert_chain, keys.remove(0)) + .with_single_cert(cert_chain, private_key) .ok()?; Some(cfg) From 00df2ad47d752b475a17f4e8313fe52d2244e5a8 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 3 Mar 2026 17:16:52 +0100 Subject: [PATCH 082/220] IDk --- src/server/omikron_connection.rs | 37 +++++++++----------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 0ae49b7..24b1754 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -1,6 +1,6 @@ use crate::server::{omikron_manager, short_link::add_short_link}; use crate::sql::{sql, sql::get_omikron_by_id, user_online_tracker}; -use crate::util::file_util::load_file_buf; +use crate::util::file_util::load_file_vec; use crate::util::{crypto_helper::encrypt, logger::PrintType}; use crate::{get_private_key, get_public_key, log_cv_in, log_cv_out, log_in}; use base64::{Engine as _, engine::general_purpose::STANDARD}; @@ -8,8 +8,9 @@ use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; use epsilon_native::{Receiver, Sender, host}; use quinn::ServerConfig; +use quinn::crypto::rustls::QuicServerConfig; use rand::{Rng, distributions::Alphanumeric}; -use rustls::pki_types::PrivateKeyDer; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use std::sync::Arc; use tokio::sync::RwLock; use x448::PublicKey; @@ -19,8 +20,8 @@ pub struct OmikronServer; impl OmikronServer { pub async fn start(port: u16) -> Result<(), Box> { let tls_cfg = OmikronServer::load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg) - .expect("Failed to convert to QuicServerConfig"); + let server_crypto = + QuicServerConfig::try_from(tls_cfg).expect("Failed to convert to QuicServerConfig"); let mut host = host(port, ServerConfig::with_crypto(Arc::new(server_crypto))).await?; tokio::spawn(async move { while let Some((sender, receiver)) = host.next().await { @@ -32,33 +33,15 @@ impl OmikronServer { Ok(()) } fn load_tls() -> Option { - let mut cert_file_buf = load_file_buf("certs", "cert.pem").ok()?; - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; + let cert_bytes = load_file_vec("certs", "cert.der").ok()?; + let key_bytes = load_file_vec("certs", "key.der").ok()?; - let cert_chain = rustls_pemfile::certs(&mut cert_file_buf) - .collect::, _>>() - .ok()?; - - let mut keys: Vec = rustls_pemfile::pkcs8_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; - - if keys.is_empty() { - let mut key_file_buf = load_file_buf("certs", "cert.key").ok()?; - keys = rustls_pemfile::rsa_private_keys(&mut key_file_buf) - .map(|k| k.map(Into::into)) - .collect::, _>>() - .ok()?; - } - - if keys.is_empty() { - return None; - } + let cert_chain = vec![CertificateDer::from(cert_bytes)]; + let private_key = PrivateKeyDer::try_from(key_bytes).ok()?; let cfg = rustls::ServerConfig::builder() .with_no_client_auth() - .with_single_cert(cert_chain, keys.remove(0)) + .with_single_cert(cert_chain, private_key) .ok()?; Some(cfg) From 3db52de92021907dede3d398a28fbf1a881491b3 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:08:20 +0100 Subject: [PATCH 083/220] [WIP] change to webtransport --- Cargo.lock | 247 +++++++++++++++++++++++++++++-- src/main.rs | 4 +- src/server/omikron_connection.rs | 122 ++++++++------- src/server/omikron_manager.rs | 2 +- 4 files changed, 308 insertions(+), 67 deletions(-) mode change 100644 => 100755 src/server/omikron_connection.rs diff --git a/Cargo.lock b/Cargo.lock index 833d221..d3cf8f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,6 +417,45 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -598,6 +637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] @@ -1001,6 +1041,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1117,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" +source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" dependencies = [ "byteorder", "quinn", @@ -1129,16 +1183,19 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" +source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" dependencies = [ "anyhow", "async-trait", + "aws-lc-rs", "bytes", "epsilon-core", "quinn", + "rcgen", "rustls", "thiserror 2.0.18", "tokio", + "wtransport", ] [[package]] @@ -1452,20 +1509,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -1625,6 +1682,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "httlib-huffman" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" + [[package]] name = "http" version = "0.2.12" @@ -2145,6 +2208,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[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" @@ -2173,6 +2242,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" +[[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.3" @@ -2182,6 +2261,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -2262,6 +2351,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "octets" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -2325,6 +2429,16 @@ dependencies = [ "hmac", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2651,6 +2765,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -2716,6 +2836,20 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2817,7 +2951,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -2862,6 +2996,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.37.28" @@ -2972,7 +3115,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3890,6 +4033,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -3926,7 +4075,7 @@ version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -4701,6 +4850,42 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wtransport" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" +dependencies = [ + "bytes", + "pem", + "quinn", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "sha2", + "socket2 0.5.10", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "url", + "wtransport-proto", + "x509-parser 0.17.0", +] + +[[package]] +name = "wtransport-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" +dependencies = [ + "httlib-huffman", + "octets", + "thiserror 2.0.18", + "url", +] + [[package]] name = "x448" version = "0.6.0" @@ -4712,6 +4897,50 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.1" diff --git a/src/main.rs b/src/main.rs index 72c42c5..aa93919 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,9 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); - let _ = omikron_connection::start(9187).await; + tokio::spawn(async move { + let _ = omikron_connection::start(9187).await; + }); log!("Started"); log!(" .env"); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs old mode 100644 new mode 100755 index c1daaca..e9c9593 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -16,11 +16,12 @@ use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; use epsilon_native::{Host, Receiver, Sender}; -use quinn::ServerConfig; use quinn::crypto::rustls::QuicServerConfig; +use quinn::{Endpoint, ServerConfig}; use rand::{Rng, distributions::Alphanumeric}; use rustls::{ServerConfig as CryptoConfig, crypto::aws_lc_rs}; use std::{ + net::SocketAddr, sync::Arc, time::{Duration, Instant}, }; @@ -105,18 +106,10 @@ pub struct OmikronConnection { id: u64, sender: Mutex>, state: RwLock, - - // Authentication state (preserved from original) challenge: RwLock, pub_key: RwLock>>, - - // Ping tracking pub ping: RwLock, - - // Waiting tasks for request/response pattern waiting_tasks: DashMap, - - // Cleanup handle cleanup_handle: Mutex>>, } @@ -189,7 +182,7 @@ impl OmikronConnection { } // Connection closed - self.cleanup().await; + self.clone().cleanup().await; log_in!( self.id as i64, PrintType::Omega, @@ -220,10 +213,10 @@ impl OmikronConnection { // Route based on authentication state match *self.state.read().await { - AuthState::Unauthenticated => self.handle_unauthenticated(cv).await, - AuthState::Identified { .. } => self.handle_identified(cv).await, + AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, + AuthState::Identified { .. } => self.clone().handle_identified(cv).await, AuthState::Authenticated { omikron_id } => { - self.handle_authenticated(cv, omikron_id).await + self.clone().handle_authenticated(cv, omikron_id).await } } } @@ -232,7 +225,7 @@ impl OmikronConnection { // Authentication Handlers // ------------------------------------------------------------------------- - async fn handle_unauthenticated(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::identification) { self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; @@ -285,7 +278,7 @@ impl OmikronConnection { self.send(&response).await } - async fn handle_identified(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::challenge_response) { self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; @@ -300,19 +293,16 @@ impl OmikronConnection { let expected_challenge = self.challenge.read().await.clone(); if client_response == expected_challenge { - // Challenge passed - mark as authenticated let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); *self.state.write().await = AuthState::Authenticated { omikron_id }; - // Register with manager - omikron_manager::add_omikron(self.arc_self()).await; + omikron_manager::add_omikron(self.clone()).await; - // Send success response let response = CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()) .add_data(DataTypes::accepted, DataValue::Bool(true)); - self.send(&response).await?; + self.clone().send(&response).await?; log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { @@ -327,7 +317,7 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn handle_authenticated( - &self, + self: Arc, cv: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { @@ -357,11 +347,9 @@ impl OmikronConnection { Ok(()) } - // Data queries CommunicationType::get_user_data => self.handle_get_user_data(cv).await, CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, - // Registration CommunicationType::get_register => self.handle_get_register(cv).await, CommunicationType::complete_register_iota => { self.handle_complete_register_iota(cv).await @@ -370,13 +358,11 @@ impl OmikronConnection { self.handle_complete_register_user(cv).await } - // Data modification CommunicationType::change_user_data => self.handle_change_user_data(cv).await, CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, CommunicationType::delete_user => self.handle_delete_user(cv).await, CommunicationType::delete_iota => self.handle_delete_iota(cv).await, - // Notifications CommunicationType::get_notifications => self.handle_get_notifications(cv).await, CommunicationType::read_notification => self.handle_read_notification(cv).await, CommunicationType::push_notification => self.handle_push_notification(cv).await, @@ -397,7 +383,7 @@ impl OmikronConnection { // Specific Handlers (ported from original WebSocket implementation) // ------------------------------------------------------------------------- - async fn handle_shorten_link(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let link = cv .get_data(DataTypes::link) .as_str() @@ -414,7 +400,7 @@ impl OmikronConnection { self.send(&response).await } - async fn handle_user_connected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { user_online_tracker::track_user_status( @@ -425,7 +411,7 @@ impl OmikronConnection { } } - async fn handle_user_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { log_in!(PrintType::Omega, "User disconnected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { @@ -438,7 +424,7 @@ impl OmikronConnection { } } - async fn handle_iota_connected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { let iota_id = iota_id as i64; @@ -468,7 +454,7 @@ impl OmikronConnection { } } - async fn handle_iota_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { let iota_id = iota_id as i64; @@ -482,7 +468,7 @@ impl OmikronConnection { } } - async fn handle_sync_status(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { for user_id_val in user_ids { if let DataValue::Number(user_id) = user_id_val { @@ -504,11 +490,14 @@ impl OmikronConnection { } } - async fn handle_get_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by user_id first if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { if let Ok(user_data) = get_by_user_id(user_id as i64).await { - let response = self.build_user_data_response(cv.get_id(), user_data).await; + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; return self.send(&response).await; } } @@ -516,7 +505,10 @@ impl OmikronConnection { // Try by username if let Some(username) = cv.get_data(DataTypes::username).as_str() { if let Ok(user_data) = get_by_username(username).await { - let response = self.build_user_data_response(cv.get_id(), user_data).await; + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; return self.send(&response).await; } } @@ -528,7 +520,7 @@ impl OmikronConnection { } async fn build_user_data_response( - &self, + self: Arc, msg_id: u32, user: ( i64, @@ -615,11 +607,12 @@ impl OmikronConnection { response } - async fn handle_get_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { let response = self + .clone() .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) .await; return self.send(&response).await; @@ -633,6 +626,7 @@ impl OmikronConnection { { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let response = self + .clone() .build_iota_data_response( cv.get_id(), iota_id, @@ -653,6 +647,7 @@ impl OmikronConnection { { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let response = self + .clone() .build_iota_data_response( cv.get_id(), iota_id, @@ -672,7 +667,7 @@ impl OmikronConnection { } async fn build_iota_data_response( - &self, + self: Arc, msg_id: u32, iota_id: i64, public_key: String, @@ -705,7 +700,7 @@ impl OmikronConnection { ) } - async fn handle_get_register(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let register_id = sql::get_register_id().await; let response = CommunicationValue::new(CommunicationType::get_register) .with_id(cv.get_id()) @@ -713,7 +708,10 @@ impl OmikronConnection { self.send(&response).await } - async fn handle_complete_register_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_complete_register_iota( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let iota_id_opt = cv .get_data(DataTypes::iota_id) .as_number() @@ -759,7 +757,10 @@ impl OmikronConnection { } } - async fn handle_complete_register_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_complete_register_user( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv .get_data(DataTypes::user_id) .as_number() @@ -803,7 +804,7 @@ impl OmikronConnection { } } - async fn handle_change_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; let mut success = true; let mut error_message = String::new(); @@ -866,7 +867,7 @@ impl OmikronConnection { } } - async fn handle_change_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( @@ -924,7 +925,7 @@ impl OmikronConnection { } } - async fn handle_delete_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; match sql::delete_user(user_id).await { Ok(_) => { @@ -941,7 +942,7 @@ impl OmikronConnection { } } - async fn handle_delete_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if let Some(iota_id) = cv .get_data(DataTypes::iota_id) .as_number() @@ -966,7 +967,10 @@ impl OmikronConnection { } } - async fn handle_get_notifications(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_notifications( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let Ok(notifications) = sql::get_notifications(user_id).await { let json_array: Vec = notifications @@ -988,7 +992,10 @@ impl OmikronConnection { } } - async fn handle_read_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_read_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let Some(other_id) = cv .get_data(DataTypes::sender_id) @@ -1007,7 +1014,10 @@ impl OmikronConnection { } } - async fn handle_push_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_push_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let Some(other_id) = cv .get_data(DataTypes::sender_id) @@ -1026,7 +1036,7 @@ impl OmikronConnection { } } - async fn handle_ping(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { *self.ping.write().await = *last_ping; } @@ -1039,7 +1049,7 @@ impl OmikronConnection { // Utilities // ------------------------------------------------------------------------- - async fn send(&self, cv: &CommunicationValue) -> OmikronResult<()> { + async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { log_cv_out!(PrintType::Omikron, cv); let guard = self.sender.lock().await; @@ -1052,7 +1062,7 @@ impl OmikronConnection { } async fn send_error_response( - &self, + self: Arc, message_id: u32, error_type: CommunicationType, ) -> OmikronResult<()> { @@ -1060,9 +1070,9 @@ impl OmikronConnection { self.send(&error).await } - pub async fn close(&self) {} + pub async fn close(self: Arc) {} - async fn cleanup(&self) { + async fn cleanup(self: Arc) { if let Some(omikron_id) = self.state.read().await.omikron_id() { if omikron_id != 0 { log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); @@ -1077,22 +1087,22 @@ impl OmikronConnection { } } - fn arc_self(&self) -> Arc { + fn arc_self(self: Arc) -> Arc { // This is a bit of a hack - in practice you'd store the Arc in the struct // or use weak references. For now, we rely on the caller having the Arc. panic!("Use the Arc directly") } // Public API for external use - pub async fn is_authenticated(&self) -> bool { + pub async fn is_authenticated(self: Arc) -> bool { self.state.read().await.is_authenticated() } - pub async fn get_omikron_id(&self) -> Option { + pub async fn get_omikron_id(self: Arc) -> Option { self.state.read().await.omikron_id() } - pub async fn send_message(&self, cv: &CommunicationValue) -> OmikronResult<()> { + pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { self.send(cv).await } } diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index 37bad20..7556674 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -8,7 +8,7 @@ pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); pub async fn add_omikron(conn: Arc) { - let id = match conn.get_omikron_id().await { + let id = match conn.clone().get_omikron_id().await { Some(id) => id, _ => { conn.close().await; From ad4631485eee35e290b0231df267dd0311a52bbb Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 3 Mar 2026 22:08:20 +0100 Subject: [PATCH 084/220] [WIP] change to webtransport --- Cargo.lock | 247 +++++++++++++++++++++++++++++-- src/main.rs | 4 +- src/server/omikron_connection.rs | 122 ++++++++------- src/server/omikron_manager.rs | 2 +- 4 files changed, 308 insertions(+), 67 deletions(-) mode change 100644 => 100755 src/server/omikron_connection.rs diff --git a/Cargo.lock b/Cargo.lock index 833d221..d3cf8f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -417,6 +417,45 @@ dependencies = [ "derive_arbitrary", ] +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -598,6 +637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" dependencies = [ "aws-lc-sys", + "untrusted 0.7.1", "zeroize", ] @@ -1001,6 +1041,20 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1117,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" +source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" dependencies = [ "byteorder", "quinn", @@ -1129,16 +1183,19 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#eb91e812e559270ff22572542491f8215db27a76" +source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" dependencies = [ "anyhow", "async-trait", + "aws-lc-rs", "bytes", "epsilon-core", "quinn", + "rcgen", "rustls", "thiserror 2.0.18", "tokio", + "wtransport", ] [[package]] @@ -1452,20 +1509,20 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 5.3.0", "wasip2", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 6.0.0", "wasip2", "wasip3", ] @@ -1625,6 +1682,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "httlib-huffman" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" + [[package]] name = "http" version = "0.2.12" @@ -2145,6 +2208,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[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" @@ -2173,6 +2242,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" +[[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.3" @@ -2182,6 +2261,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -2262,6 +2351,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "octets" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -2325,6 +2429,16 @@ dependencies = [ "hmac", ] +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2651,6 +2765,12 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" version = "0.8.5" @@ -2716,6 +2836,20 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", + "yasna", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2817,7 +2951,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.17", "libc", - "untrusted", + "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -2862,6 +2996,15 @@ dependencies = [ "semver", ] +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + [[package]] name = "rustix" version = "0.37.28" @@ -2972,7 +3115,7 @@ dependencies = [ "aws-lc-rs", "ring", "rustls-pki-types", - "untrusted", + "untrusted 0.9.0", ] [[package]] @@ -3890,6 +4033,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "untrusted" version = "0.9.0" @@ -3926,7 +4075,7 @@ version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.4.1", + "getrandom 0.4.2", "js-sys", "wasm-bindgen", ] @@ -4701,6 +4850,42 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +[[package]] +name = "wtransport" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" +dependencies = [ + "bytes", + "pem", + "quinn", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "sha2", + "socket2 0.5.10", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "url", + "wtransport-proto", + "x509-parser 0.17.0", +] + +[[package]] +name = "wtransport-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" +dependencies = [ + "httlib-huffman", + "octets", + "thiserror 2.0.18", + "url", +] + [[package]] name = "x448" version = "0.6.0" @@ -4712,6 +4897,50 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "ring", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + [[package]] name = "yoke" version = "0.8.1" diff --git a/src/main.rs b/src/main.rs index 72c42c5..aa93919 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,9 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); - let _ = omikron_connection::start(9187).await; + tokio::spawn(async move { + let _ = omikron_connection::start(9187).await; + }); log!("Started"); log!(" .env"); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs old mode 100644 new mode 100755 index c1daaca..e9c9593 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -16,11 +16,12 @@ use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; use epsilon_native::{Host, Receiver, Sender}; -use quinn::ServerConfig; use quinn::crypto::rustls::QuicServerConfig; +use quinn::{Endpoint, ServerConfig}; use rand::{Rng, distributions::Alphanumeric}; use rustls::{ServerConfig as CryptoConfig, crypto::aws_lc_rs}; use std::{ + net::SocketAddr, sync::Arc, time::{Duration, Instant}, }; @@ -105,18 +106,10 @@ pub struct OmikronConnection { id: u64, sender: Mutex>, state: RwLock, - - // Authentication state (preserved from original) challenge: RwLock, pub_key: RwLock>>, - - // Ping tracking pub ping: RwLock, - - // Waiting tasks for request/response pattern waiting_tasks: DashMap, - - // Cleanup handle cleanup_handle: Mutex>>, } @@ -189,7 +182,7 @@ impl OmikronConnection { } // Connection closed - self.cleanup().await; + self.clone().cleanup().await; log_in!( self.id as i64, PrintType::Omega, @@ -220,10 +213,10 @@ impl OmikronConnection { // Route based on authentication state match *self.state.read().await { - AuthState::Unauthenticated => self.handle_unauthenticated(cv).await, - AuthState::Identified { .. } => self.handle_identified(cv).await, + AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, + AuthState::Identified { .. } => self.clone().handle_identified(cv).await, AuthState::Authenticated { omikron_id } => { - self.handle_authenticated(cv, omikron_id).await + self.clone().handle_authenticated(cv, omikron_id).await } } } @@ -232,7 +225,7 @@ impl OmikronConnection { // Authentication Handlers // ------------------------------------------------------------------------- - async fn handle_unauthenticated(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::identification) { self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; @@ -285,7 +278,7 @@ impl OmikronConnection { self.send(&response).await } - async fn handle_identified(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::challenge_response) { self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; @@ -300,19 +293,16 @@ impl OmikronConnection { let expected_challenge = self.challenge.read().await.clone(); if client_response == expected_challenge { - // Challenge passed - mark as authenticated let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); *self.state.write().await = AuthState::Authenticated { omikron_id }; - // Register with manager - omikron_manager::add_omikron(self.arc_self()).await; + omikron_manager::add_omikron(self.clone()).await; - // Send success response let response = CommunicationValue::new(CommunicationType::identification_response) .with_id(cv.get_id()) .add_data(DataTypes::accepted, DataValue::Bool(true)); - self.send(&response).await?; + self.clone().send(&response).await?; log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { @@ -327,7 +317,7 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn handle_authenticated( - &self, + self: Arc, cv: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { @@ -357,11 +347,9 @@ impl OmikronConnection { Ok(()) } - // Data queries CommunicationType::get_user_data => self.handle_get_user_data(cv).await, CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, - // Registration CommunicationType::get_register => self.handle_get_register(cv).await, CommunicationType::complete_register_iota => { self.handle_complete_register_iota(cv).await @@ -370,13 +358,11 @@ impl OmikronConnection { self.handle_complete_register_user(cv).await } - // Data modification CommunicationType::change_user_data => self.handle_change_user_data(cv).await, CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, CommunicationType::delete_user => self.handle_delete_user(cv).await, CommunicationType::delete_iota => self.handle_delete_iota(cv).await, - // Notifications CommunicationType::get_notifications => self.handle_get_notifications(cv).await, CommunicationType::read_notification => self.handle_read_notification(cv).await, CommunicationType::push_notification => self.handle_push_notification(cv).await, @@ -397,7 +383,7 @@ impl OmikronConnection { // Specific Handlers (ported from original WebSocket implementation) // ------------------------------------------------------------------------- - async fn handle_shorten_link(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let link = cv .get_data(DataTypes::link) .as_str() @@ -414,7 +400,7 @@ impl OmikronConnection { self.send(&response).await } - async fn handle_user_connected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { user_online_tracker::track_user_status( @@ -425,7 +411,7 @@ impl OmikronConnection { } } - async fn handle_user_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { log_in!(PrintType::Omega, "User disconnected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { @@ -438,7 +424,7 @@ impl OmikronConnection { } } - async fn handle_iota_connected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { let iota_id = iota_id as i64; @@ -468,7 +454,7 @@ impl OmikronConnection { } } - async fn handle_iota_disconnected(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { let iota_id = iota_id as i64; @@ -482,7 +468,7 @@ impl OmikronConnection { } } - async fn handle_sync_status(&self, cv: CommunicationValue, omikron_id: i64) { + async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { for user_id_val in user_ids { if let DataValue::Number(user_id) = user_id_val { @@ -504,11 +490,14 @@ impl OmikronConnection { } } - async fn handle_get_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by user_id first if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { if let Ok(user_data) = get_by_user_id(user_id as i64).await { - let response = self.build_user_data_response(cv.get_id(), user_data).await; + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; return self.send(&response).await; } } @@ -516,7 +505,10 @@ impl OmikronConnection { // Try by username if let Some(username) = cv.get_data(DataTypes::username).as_str() { if let Ok(user_data) = get_by_username(username).await { - let response = self.build_user_data_response(cv.get_id(), user_data).await; + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; return self.send(&response).await; } } @@ -528,7 +520,7 @@ impl OmikronConnection { } async fn build_user_data_response( - &self, + self: Arc, msg_id: u32, user: ( i64, @@ -615,11 +607,12 @@ impl OmikronConnection { response } - async fn handle_get_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { let response = self + .clone() .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) .await; return self.send(&response).await; @@ -633,6 +626,7 @@ impl OmikronConnection { { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let response = self + .clone() .build_iota_data_response( cv.get_id(), iota_id, @@ -653,6 +647,7 @@ impl OmikronConnection { { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { let response = self + .clone() .build_iota_data_response( cv.get_id(), iota_id, @@ -672,7 +667,7 @@ impl OmikronConnection { } async fn build_iota_data_response( - &self, + self: Arc, msg_id: u32, iota_id: i64, public_key: String, @@ -705,7 +700,7 @@ impl OmikronConnection { ) } - async fn handle_get_register(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let register_id = sql::get_register_id().await; let response = CommunicationValue::new(CommunicationType::get_register) .with_id(cv.get_id()) @@ -713,7 +708,10 @@ impl OmikronConnection { self.send(&response).await } - async fn handle_complete_register_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_complete_register_iota( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let iota_id_opt = cv .get_data(DataTypes::iota_id) .as_number() @@ -759,7 +757,10 @@ impl OmikronConnection { } } - async fn handle_complete_register_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_complete_register_user( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv .get_data(DataTypes::user_id) .as_number() @@ -803,7 +804,7 @@ impl OmikronConnection { } } - async fn handle_change_user_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; let mut success = true; let mut error_message = String::new(); @@ -866,7 +867,7 @@ impl OmikronConnection { } } - async fn handle_change_iota_data(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( @@ -924,7 +925,7 @@ impl OmikronConnection { } } - async fn handle_delete_user(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; match sql::delete_user(user_id).await { Ok(_) => { @@ -941,7 +942,7 @@ impl OmikronConnection { } } - async fn handle_delete_iota(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if let Some(iota_id) = cv .get_data(DataTypes::iota_id) .as_number() @@ -966,7 +967,10 @@ impl OmikronConnection { } } - async fn handle_get_notifications(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_get_notifications( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let Ok(notifications) = sql::get_notifications(user_id).await { let json_array: Vec = notifications @@ -988,7 +992,10 @@ impl OmikronConnection { } } - async fn handle_read_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_read_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let Some(other_id) = cv .get_data(DataTypes::sender_id) @@ -1007,7 +1014,10 @@ impl OmikronConnection { } } - async fn handle_push_notification(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_push_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; if let Some(other_id) = cv .get_data(DataTypes::sender_id) @@ -1026,7 +1036,7 @@ impl OmikronConnection { } } - async fn handle_ping(&self, cv: CommunicationValue) -> OmikronResult<()> { + async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { *self.ping.write().await = *last_ping; } @@ -1039,7 +1049,7 @@ impl OmikronConnection { // Utilities // ------------------------------------------------------------------------- - async fn send(&self, cv: &CommunicationValue) -> OmikronResult<()> { + async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { log_cv_out!(PrintType::Omikron, cv); let guard = self.sender.lock().await; @@ -1052,7 +1062,7 @@ impl OmikronConnection { } async fn send_error_response( - &self, + self: Arc, message_id: u32, error_type: CommunicationType, ) -> OmikronResult<()> { @@ -1060,9 +1070,9 @@ impl OmikronConnection { self.send(&error).await } - pub async fn close(&self) {} + pub async fn close(self: Arc) {} - async fn cleanup(&self) { + async fn cleanup(self: Arc) { if let Some(omikron_id) = self.state.read().await.omikron_id() { if omikron_id != 0 { log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); @@ -1077,22 +1087,22 @@ impl OmikronConnection { } } - fn arc_self(&self) -> Arc { + fn arc_self(self: Arc) -> Arc { // This is a bit of a hack - in practice you'd store the Arc in the struct // or use weak references. For now, we rely on the caller having the Arc. panic!("Use the Arc directly") } // Public API for external use - pub async fn is_authenticated(&self) -> bool { + pub async fn is_authenticated(self: Arc) -> bool { self.state.read().await.is_authenticated() } - pub async fn get_omikron_id(&self) -> Option { + pub async fn get_omikron_id(self: Arc) -> Option { self.state.read().await.omikron_id() } - pub async fn send_message(&self, cv: &CommunicationValue) -> OmikronResult<()> { + pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { self.send(cv).await } } diff --git a/src/server/omikron_manager.rs b/src/server/omikron_manager.rs index 37bad20..7556674 100644 --- a/src/server/omikron_manager.rs +++ b/src/server/omikron_manager.rs @@ -8,7 +8,7 @@ pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); pub async fn add_omikron(conn: Arc) { - let id = match conn.get_omikron_id().await { + let id = match conn.clone().get_omikron_id().await { Some(id) => id, _ => { conn.close().await; From 50a562d092623aa0f58875f23820d2eefa58ffa6 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:12:22 +0100 Subject: [PATCH 085/220] [WIP] WTransport --- Cargo.lock | 11 +++--- src/main.rs | 11 +++++- src/server/omikron_connection.rs | 58 ++++++++------------------------ 3 files changed, 31 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3cf8f0..03fb885 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#fb490bac71a652ecd705875baab910b753430164" dependencies = [ "byteorder", "quinn", @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#fb490bac71a652ecd705875baab910b753430164" dependencies = [ "anyhow", "async-trait", @@ -1196,6 +1196,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "wtransport", + "wtransport-proto", ] [[package]] @@ -2752,9 +2753,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2842,6 +2843,7 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ + "aws-lc-rs", "pem", "ring", "rustls-pki-types", @@ -4921,6 +4923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", + "aws-lc-rs", "data-encoding", "der-parser", "lazy_static", diff --git a/src/main.rs b/src/main.rs index aa93919..866d216 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,11 @@ use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::util::crypto_helper::load_public_key; use crate::util::crypto_helper::load_secret_key; +use crate::util::logger::PrintType; use crate::util::logger::startup; use dotenv::dotenv; use once_cell::sync::Lazy; +use rustls::crypto::aws_lc_rs::default_provider; use std::env; static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); @@ -23,13 +25,20 @@ pub fn get_public_key() -> x448::PublicKey { #[tokio::main] async fn main() { + if let Err(_) = default_provider().install_default() { + println!("Error loading Provider"); + return; + } dotenv().ok(); startup(); log_in!("Incoming messages"); log_out!("Outgoing messages"); tokio::spawn(async move { - let _ = omikron_connection::start(9187).await; + match omikron_connection::start(9187).await { + Err(e) => log_err!(0, PrintType::General, "{:?}", e), + _ => {} + } }); log!("Started"); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e9c9593..9c2756b 100755 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -6,22 +6,14 @@ use crate::{ sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, user_online_tracker::{self}, }, - util::{ - crypto_helper::encrypt, - file_util::{load_file_buf, load_file_vec}, - logger::PrintType, - }, + util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; use epsilon_native::{Host, Receiver, Sender}; -use quinn::crypto::rustls::QuicServerConfig; -use quinn::{Endpoint, ServerConfig}; use rand::{Rng, distributions::Alphanumeric}; -use rustls::{ServerConfig as CryptoConfig, crypto::aws_lc_rs}; use std::{ - net::SocketAddr, sync::Arc, time::{Duration, Instant}, }; @@ -152,7 +144,7 @@ impl OmikronConnection { // Main Handler Loop // ------------------------------------------------------------------------- - pub async fn handle(self: Arc, mut receiver: Receiver) { + pub async fn handle(self: Arc, receiver: Receiver) { log_in!( self.id as i64, PrintType::Omega, @@ -227,7 +219,8 @@ impl OmikronConnection { async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::identification) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -280,7 +273,8 @@ impl OmikronConnection { async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::challenge_response) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -306,7 +300,8 @@ impl OmikronConnection { log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) .await; Err(OmikronError::AuthenticationFailed) } @@ -1087,12 +1082,6 @@ impl OmikronConnection { } } - fn arc_self(self: Arc) -> Arc { - // This is a bit of a hack - in practice you'd store the Arc in the struct - // or use weak references. For now, we rely on the caller having the Arc. - panic!("Use the Arc directly") - } - // Public API for external use pub async fn is_authenticated(self: Arc) -> bool { self.state.read().await.is_authenticated() @@ -1112,13 +1101,13 @@ impl OmikronConnection { // ============================================================================ pub async fn start(port: u16) -> Result<(), Box> { - let _ = aws_lc_rs::default_provider().install_default(); + let cert_pem = load_file_vec("certs", "cert.pem") + .map_err(|e| format!("Failed to load certificate: {}", e))?; + let key_pem = load_file_vec("certs", "key.pem") + .map_err(|e| format!("Failed to load private key: {}", e))?; - let tls_cfg = load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg)?; - let server_cfg = ServerConfig::with_crypto(Arc::new(server_crypto)); - - let mut host: Host = epsilon_native::host(port, server_cfg).await?; + log!("OmikronServer starting on port {}", port); + let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); while let Some((sender, receiver)) = host.next().await { @@ -1130,22 +1119,3 @@ pub async fn start(port: u16) -> Result<(), Box> { Ok(()) } - -fn load_tls() -> Option { - let _ = aws_lc_rs::default_provider().install_default(); - - let mut cert_pem = load_file_buf("certs", "cert.pem").ok()?; - let cert_chain = rustls_pemfile::certs(&mut cert_pem) - .collect::, _>>() - .ok()?; - - let key_pem = load_file_vec("certs", "key.pem").ok()?; - let key_der = rustls_pemfile::private_key(&mut &*key_pem).ok()??; - - let cfg = CryptoConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key_der) - .ok()?; - - Some(cfg) -} From f379a0c015fd86d4397719453951016bad7c9692 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 4 Mar 2026 17:12:22 +0100 Subject: [PATCH 086/220] [WIP] WTransport --- Cargo.lock | 11 +++--- src/main.rs | 11 +++++- src/server/omikron_connection.rs | 58 ++++++++------------------------ 3 files changed, 31 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3cf8f0..03fb885 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#fb490bac71a652ecd705875baab910b753430164" dependencies = [ "byteorder", "quinn", @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#fb490bac71a652ecd705875baab910b753430164" dependencies = [ "anyhow", "async-trait", @@ -1196,6 +1196,7 @@ dependencies = [ "thiserror 2.0.18", "tokio", "wtransport", + "wtransport-proto", ] [[package]] @@ -2752,9 +2753,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2842,6 +2843,7 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ + "aws-lc-rs", "pem", "ring", "rustls-pki-types", @@ -4921,6 +4923,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", + "aws-lc-rs", "data-encoding", "der-parser", "lazy_static", diff --git a/src/main.rs b/src/main.rs index aa93919..866d216 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,11 @@ use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::util::crypto_helper::load_public_key; use crate::util::crypto_helper::load_secret_key; +use crate::util::logger::PrintType; use crate::util::logger::startup; use dotenv::dotenv; use once_cell::sync::Lazy; +use rustls::crypto::aws_lc_rs::default_provider; use std::env; static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); @@ -23,13 +25,20 @@ pub fn get_public_key() -> x448::PublicKey { #[tokio::main] async fn main() { + if let Err(_) = default_provider().install_default() { + println!("Error loading Provider"); + return; + } dotenv().ok(); startup(); log_in!("Incoming messages"); log_out!("Outgoing messages"); tokio::spawn(async move { - let _ = omikron_connection::start(9187).await; + match omikron_connection::start(9187).await { + Err(e) => log_err!(0, PrintType::General, "{:?}", e), + _ => {} + } }); log!("Started"); diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e9c9593..9c2756b 100755 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -6,22 +6,14 @@ use crate::{ sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, user_online_tracker::{self}, }, - util::{ - crypto_helper::encrypt, - file_util::{load_file_buf, load_file_vec}, - logger::PrintType, - }, + util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; use epsilon_native::{Host, Receiver, Sender}; -use quinn::crypto::rustls::QuicServerConfig; -use quinn::{Endpoint, ServerConfig}; use rand::{Rng, distributions::Alphanumeric}; -use rustls::{ServerConfig as CryptoConfig, crypto::aws_lc_rs}; use std::{ - net::SocketAddr, sync::Arc, time::{Duration, Instant}, }; @@ -152,7 +144,7 @@ impl OmikronConnection { // Main Handler Loop // ------------------------------------------------------------------------- - pub async fn handle(self: Arc, mut receiver: Receiver) { + pub async fn handle(self: Arc, receiver: Receiver) { log_in!( self.id as i64, PrintType::Omega, @@ -227,7 +219,8 @@ impl OmikronConnection { async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::identification) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -280,7 +273,8 @@ impl OmikronConnection { async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::challenge_response) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -306,7 +300,8 @@ impl OmikronConnection { log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) .await; Err(OmikronError::AuthenticationFailed) } @@ -1087,12 +1082,6 @@ impl OmikronConnection { } } - fn arc_self(self: Arc) -> Arc { - // This is a bit of a hack - in practice you'd store the Arc in the struct - // or use weak references. For now, we rely on the caller having the Arc. - panic!("Use the Arc directly") - } - // Public API for external use pub async fn is_authenticated(self: Arc) -> bool { self.state.read().await.is_authenticated() @@ -1112,13 +1101,13 @@ impl OmikronConnection { // ============================================================================ pub async fn start(port: u16) -> Result<(), Box> { - let _ = aws_lc_rs::default_provider().install_default(); + let cert_pem = load_file_vec("certs", "cert.pem") + .map_err(|e| format!("Failed to load certificate: {}", e))?; + let key_pem = load_file_vec("certs", "key.pem") + .map_err(|e| format!("Failed to load private key: {}", e))?; - let tls_cfg = load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg)?; - let server_cfg = ServerConfig::with_crypto(Arc::new(server_crypto)); - - let mut host: Host = epsilon_native::host(port, server_cfg).await?; + log!("OmikronServer starting on port {}", port); + let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); while let Some((sender, receiver)) = host.next().await { @@ -1130,22 +1119,3 @@ pub async fn start(port: u16) -> Result<(), Box> { Ok(()) } - -fn load_tls() -> Option { - let _ = aws_lc_rs::default_provider().install_default(); - - let mut cert_pem = load_file_buf("certs", "cert.pem").ok()?; - let cert_chain = rustls_pemfile::certs(&mut cert_pem) - .collect::, _>>() - .ok()?; - - let key_pem = load_file_vec("certs", "key.pem").ok()?; - let key_der = rustls_pemfile::private_key(&mut &*key_pem).ok()??; - - let cfg = CryptoConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key_der) - .ok()?; - - Some(cfg) -} From f181fbea9b02f53bff7d965cf22fa6ded409e586 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:35:20 +0100 Subject: [PATCH 087/220] [FIX] WTRANSPORT WORKS (it was Alois fault) --- Cargo.lock | 32 +++++++++++++++------------ src/server/omikron_connection.rs | 38 +++++++++----------------------- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3cf8f0..8c81204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,7 +266,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.2", + "socket2 0.6.3", "time", "tracing", "url", @@ -1171,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" dependencies = [ "byteorder", "quinn", @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" dependencies = [ "anyhow", "async-trait", @@ -1193,9 +1193,11 @@ dependencies = [ "quinn", "rcgen", "rustls", + "rustls-native-certs", "thiserror 2.0.18", "tokio", "wtransport", + "wtransport-proto", ] [[package]] @@ -1799,7 +1801,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -2705,7 +2707,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.2", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -2745,16 +2747,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2842,6 +2844,7 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ + "aws-lc-rs", "pem", "ring", "rustls-pki-types", @@ -3344,12 +3347,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3801,7 +3804,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] @@ -4071,9 +4074,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -4921,6 +4924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", + "aws-lc-rs", "data-encoding", "der-parser", "lazy_static", diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e9c9593..0a7fa87 100755 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -152,7 +152,7 @@ impl OmikronConnection { // Main Handler Loop // ------------------------------------------------------------------------- - pub async fn handle(self: Arc, mut receiver: Receiver) { + pub async fn handle(self: Arc, receiver: Receiver) { log_in!( self.id as i64, PrintType::Omega, @@ -227,7 +227,8 @@ impl OmikronConnection { async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::identification) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -280,7 +281,8 @@ impl OmikronConnection { async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::challenge_response) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -306,7 +308,8 @@ impl OmikronConnection { log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) .await; Err(OmikronError::AuthenticationFailed) } @@ -1114,11 +1117,11 @@ impl OmikronConnection { pub async fn start(port: u16) -> Result<(), Box> { let _ = aws_lc_rs::default_provider().install_default(); - let tls_cfg = load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg)?; - let server_cfg = ServerConfig::with_crypto(Arc::new(server_crypto)); + let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); - let mut host: Host = epsilon_native::host(port, server_cfg).await?; + let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); + + let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); while let Some((sender, receiver)) = host.next().await { @@ -1130,22 +1133,3 @@ pub async fn start(port: u16) -> Result<(), Box> { Ok(()) } - -fn load_tls() -> Option { - let _ = aws_lc_rs::default_provider().install_default(); - - let mut cert_pem = load_file_buf("certs", "cert.pem").ok()?; - let cert_chain = rustls_pemfile::certs(&mut cert_pem) - .collect::, _>>() - .ok()?; - - let key_pem = load_file_vec("certs", "key.pem").ok()?; - let key_der = rustls_pemfile::private_key(&mut &*key_pem).ok()??; - - let cfg = CryptoConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key_der) - .ok()?; - - Some(cfg) -} From 7c8751ff1da871aa5f122cfc1d501a176c2fd58b Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 6 Mar 2026 20:35:20 +0100 Subject: [PATCH 088/220] [FIX] WTRANSPORT WORKS (it was Alois fault) --- Cargo.lock | 32 +++++++++++++++------------ src/server/omikron_connection.rs | 38 +++++++++----------------------- 2 files changed, 29 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3cf8f0..8c81204 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -266,7 +266,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.2", + "socket2 0.6.3", "time", "tracing", "url", @@ -1171,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" dependencies = [ "byteorder", "quinn", @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#4bdd32b44a77ac980cede20a1d401004f52e4a29" +source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" dependencies = [ "anyhow", "async-trait", @@ -1193,9 +1193,11 @@ dependencies = [ "quinn", "rcgen", "rustls", + "rustls-native-certs", "thiserror 2.0.18", "tokio", "wtransport", + "wtransport-proto", ] [[package]] @@ -1799,7 +1801,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.2", + "socket2 0.6.3", "system-configuration", "tokio", "tower-service", @@ -2705,7 +2707,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.2", + "socket2 0.6.3", "thiserror 2.0.18", "tokio", "tracing", @@ -2745,16 +2747,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.2", + "socket2 0.6.3", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -2842,6 +2844,7 @@ version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ + "aws-lc-rs", "pem", "ring", "rustls-pki-types", @@ -3344,12 +3347,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -3801,7 +3804,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.2", + "socket2 0.6.3", "tokio-macros", "windows-sys 0.61.2", ] @@ -4071,9 +4074,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.21.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -4921,6 +4924,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" dependencies = [ "asn1-rs", + "aws-lc-rs", "data-encoding", "der-parser", "lazy_static", diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index e9c9593..0a7fa87 100755 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -152,7 +152,7 @@ impl OmikronConnection { // Main Handler Loop // ------------------------------------------------------------------------- - pub async fn handle(self: Arc, mut receiver: Receiver) { + pub async fn handle(self: Arc, receiver: Receiver) { log_in!( self.id as i64, PrintType::Omega, @@ -227,7 +227,8 @@ impl OmikronConnection { async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::identification) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -280,7 +281,8 @@ impl OmikronConnection { async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::challenge_response) { - self.send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) .await; return Err(OmikronError::NotAuthenticated); } @@ -306,7 +308,8 @@ impl OmikronConnection { log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) .await; Err(OmikronError::AuthenticationFailed) } @@ -1114,11 +1117,11 @@ impl OmikronConnection { pub async fn start(port: u16) -> Result<(), Box> { let _ = aws_lc_rs::default_provider().install_default(); - let tls_cfg = load_tls().expect("TLS config failed"); - let server_crypto = quinn::crypto::rustls::QuicServerConfig::try_from(tls_cfg)?; - let server_cfg = ServerConfig::with_crypto(Arc::new(server_crypto)); + let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); - let mut host: Host = epsilon_native::host(port, server_cfg).await?; + let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); + + let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); while let Some((sender, receiver)) = host.next().await { @@ -1130,22 +1133,3 @@ pub async fn start(port: u16) -> Result<(), Box> { Ok(()) } - -fn load_tls() -> Option { - let _ = aws_lc_rs::default_provider().install_default(); - - let mut cert_pem = load_file_buf("certs", "cert.pem").ok()?; - let cert_chain = rustls_pemfile::certs(&mut cert_pem) - .collect::, _>>() - .ok()?; - - let key_pem = load_file_vec("certs", "key.pem").ok()?; - let key_der = rustls_pemfile::private_key(&mut &*key_pem).ok()??; - - let cfg = CryptoConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key_der) - .ok()?; - - Some(cfg) -} From b9402fb289b425e2f830883a51048485f1df96eb Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:00:56 +0100 Subject: [PATCH 089/220] [Fix] Omikron & Omega Connection --- Cargo.lock | 4 +- src/server/omikron_connection.rs | 83 ++++++++++++++++++-------------- src/util/logger.rs | 4 +- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c81204..a353f19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" +source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" dependencies = [ "byteorder", "quinn", @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" +source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" dependencies = [ "anyhow", "async-trait", diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 0b200a8..5b4ddc3 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -122,21 +122,6 @@ impl OmikronConnection { cleanup_handle: Mutex::new(None), }); - // Start cleanup task for waiting tasks - let cleanup_conn = conn.clone(); - let handle = tokio::spawn(async move { - let mut ticker = interval(CLEANUP_INTERVAL); - loop { - ticker.tick().await; - cleanup_conn - .waiting_tasks - .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); - } - }); - - // Store handle (would need block_in_place or similar to set this immediately) - // For now, we'll handle this differently in handle() - conn } @@ -144,7 +129,7 @@ impl OmikronConnection { // Main Handler Loop // ------------------------------------------------------------------------- - pub async fn handle(self: Arc, receiver: Receiver) { + pub async fn handle(self: Arc, receiver: &mut Receiver) { log_in!( self.id as i64, PrintType::Omega, @@ -153,7 +138,7 @@ impl OmikronConnection { // Start cleanup task let cleanup_conn = self.clone(); - let _cleanup_handle = tokio::spawn(async move { + let cleanup_handle = tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); loop { ticker.tick().await; @@ -162,23 +147,22 @@ impl OmikronConnection { .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); } }); + *self.cleanup_handle.lock().await = Some(cleanup_handle); while let Ok(cv) = receiver.receive().await { if let Err(e) = self.clone().process_message(cv).await { log_err!(0, PrintType::Omega, "Error processing message: {}", e); - // Don't break on error unless critical - match original WebSocket behavior if matches!(e, OmikronError::NotConnected) { break; } } } - // Connection closed self.clone().cleanup().await; log_in!( self.id as i64, PrintType::Omega, - "Omikron connection closed" + "Omikron connection closed" ); } @@ -203,8 +187,9 @@ impl OmikronConnection { return self.handle_ping(cv).await; } + let current_state = *self.state.read().await; // Route based on authentication state - match *self.state.read().await { + match current_state { AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, AuthState::Identified { .. } => self.clone().handle_identified(cv).await, AuthState::Authenticated { omikron_id } => { @@ -227,21 +212,27 @@ impl OmikronConnection { // Extract omikron ID let omikron_id = cv - .get_data(DataTypes::omikron) + .get_data(DataTypes::omikron_id) .as_number() .ok_or(OmikronError::InvalidResponse)?; + log!("Omikron {:?} connected", omikron_id); // Lookup omikron in database let (public_key, _) = get_omikron_by_id(omikron_id) .await .map_err(|e| OmikronError::Sql(e.to_string()))?; + log!("Got public Key"); + let pub_key_bytes = STANDARD .decode(&public_key) .map_err(|_| OmikronError::AuthenticationFailed)?; - let omikron_pub_key = - PublicKey::from_bytes(&pub_key_bytes).ok_or(OmikronError::AuthenticationFailed)?; + let pub_key_bytes_clone = pub_key_bytes.clone(); + let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) + .ok_or(OmikronError::AuthenticationFailed)?; + + log!("Decoded public Key"); // Generate challenge let challenge: String = rand::thread_rng() @@ -250,14 +241,32 @@ impl OmikronConnection { .map(char::from) .collect(); - // Store state + log!("Generated Challenge"); + *self.challenge.write().await = challenge.clone(); + + log!("Stored Challenge"); + *self.pub_key.write().await = Some(pub_key_bytes); + + log!("Stored Pubkey"); + *self.state.write().await = AuthState::Identified { omikron_id }; - // Encrypt challenge - let encrypted = encrypt(get_private_key(), omikron_pub_key, &challenge) - .map_err(|_| OmikronError::AuthenticationFailed)?; + log!("Stored State"); + + let challenge_clone = challenge.clone(); + let private_key = get_private_key(); + let public_key_for_encrypt = omikron_pub_key; + + let encrypted = tokio::task::spawn_blocking(move || { + encrypt(private_key, public_key_for_encrypt, &challenge_clone) + .map_err(|_| OmikronError::AuthenticationFailed) + }) + .await + .map_err(|_| OmikronError::AuthenticationFailed)??; + + log!("Encrypted Challenge"); // Send challenge response let response = CommunicationValue::new(CommunicationType::challenge) @@ -268,6 +277,7 @@ impl OmikronConnection { ) .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + log!("Sending Challenge"); self.send(&response).await } @@ -1065,7 +1075,13 @@ impl OmikronConnection { self.send(&error).await } - pub async fn close(self: Arc) {} + pub async fn close(self: Arc) { + log_in!( + self.get_omikron_id().await.unwrap_or(0), + PrintType::Omega, + "Omikron connection Closed" + ); + } async fn cleanup(self: Arc) { if let Some(omikron_id) = self.state.read().await.omikron_id() { @@ -1101,11 +1117,6 @@ impl OmikronConnection { // ============================================================================ pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "cert.pem") - .map_err(|e| format!("Failed to load certificate: {}", e))?; - let key_pem = load_file_vec("certs", "key.pem") - .map_err(|e| format!("Failed to load private key: {}", e))?; - let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); @@ -1113,10 +1124,10 @@ pub async fn start(port: u16) -> Result<(), Box> { let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); - while let Some((sender, receiver)) = host.next().await { + while let Some((sender, mut receiver)) = host.next().await { tokio::spawn(async move { let conn = OmikronConnection::new(sender); - conn.handle(receiver).await; + conn.handle(&mut receiver).await; }); } diff --git a/src/util/logger.rs b/src/util/logger.rs index e861e69..d1a7764 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, fs::{self, OpenOptions}, io::Write, path::Path, @@ -249,7 +249,7 @@ pub fn format_cv(cv: &CommunicationValue) -> String { let comm_type = cv.get_type().to_string(); parts.push(format!("{}", comm_type)); - let data: &HashMap = cv.get_data_container(); + let data: &BTreeMap = cv.get_data_container(); let formated_data = format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); From 2784cc030b26837381701627bfbf5d491b95e0e3 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 7 Mar 2026 22:00:56 +0100 Subject: [PATCH 090/220] [Fix] Omikron & Omega Connection --- Cargo.lock | 4 +- src/server/omikron_connection.rs | 83 ++++++++++++++++++-------------- src/util/logger.rs | 4 +- 3 files changed, 51 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c81204..a353f19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1171,7 +1171,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" +source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" dependencies = [ "byteorder", "quinn", @@ -1183,7 +1183,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#44e3a3ec0ee6ac4c00f8cfb7c20c3299c7ae89c5" +source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" dependencies = [ "anyhow", "async-trait", diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 0b200a8..5b4ddc3 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -122,21 +122,6 @@ impl OmikronConnection { cleanup_handle: Mutex::new(None), }); - // Start cleanup task for waiting tasks - let cleanup_conn = conn.clone(); - let handle = tokio::spawn(async move { - let mut ticker = interval(CLEANUP_INTERVAL); - loop { - ticker.tick().await; - cleanup_conn - .waiting_tasks - .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); - } - }); - - // Store handle (would need block_in_place or similar to set this immediately) - // For now, we'll handle this differently in handle() - conn } @@ -144,7 +129,7 @@ impl OmikronConnection { // Main Handler Loop // ------------------------------------------------------------------------- - pub async fn handle(self: Arc, receiver: Receiver) { + pub async fn handle(self: Arc, receiver: &mut Receiver) { log_in!( self.id as i64, PrintType::Omega, @@ -153,7 +138,7 @@ impl OmikronConnection { // Start cleanup task let cleanup_conn = self.clone(); - let _cleanup_handle = tokio::spawn(async move { + let cleanup_handle = tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); loop { ticker.tick().await; @@ -162,23 +147,22 @@ impl OmikronConnection { .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); } }); + *self.cleanup_handle.lock().await = Some(cleanup_handle); while let Ok(cv) = receiver.receive().await { if let Err(e) = self.clone().process_message(cv).await { log_err!(0, PrintType::Omega, "Error processing message: {}", e); - // Don't break on error unless critical - match original WebSocket behavior if matches!(e, OmikronError::NotConnected) { break; } } } - // Connection closed self.clone().cleanup().await; log_in!( self.id as i64, PrintType::Omega, - "Omikron connection closed" + "Omikron connection closed" ); } @@ -203,8 +187,9 @@ impl OmikronConnection { return self.handle_ping(cv).await; } + let current_state = *self.state.read().await; // Route based on authentication state - match *self.state.read().await { + match current_state { AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, AuthState::Identified { .. } => self.clone().handle_identified(cv).await, AuthState::Authenticated { omikron_id } => { @@ -227,21 +212,27 @@ impl OmikronConnection { // Extract omikron ID let omikron_id = cv - .get_data(DataTypes::omikron) + .get_data(DataTypes::omikron_id) .as_number() .ok_or(OmikronError::InvalidResponse)?; + log!("Omikron {:?} connected", omikron_id); // Lookup omikron in database let (public_key, _) = get_omikron_by_id(omikron_id) .await .map_err(|e| OmikronError::Sql(e.to_string()))?; + log!("Got public Key"); + let pub_key_bytes = STANDARD .decode(&public_key) .map_err(|_| OmikronError::AuthenticationFailed)?; - let omikron_pub_key = - PublicKey::from_bytes(&pub_key_bytes).ok_or(OmikronError::AuthenticationFailed)?; + let pub_key_bytes_clone = pub_key_bytes.clone(); + let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) + .ok_or(OmikronError::AuthenticationFailed)?; + + log!("Decoded public Key"); // Generate challenge let challenge: String = rand::thread_rng() @@ -250,14 +241,32 @@ impl OmikronConnection { .map(char::from) .collect(); - // Store state + log!("Generated Challenge"); + *self.challenge.write().await = challenge.clone(); + + log!("Stored Challenge"); + *self.pub_key.write().await = Some(pub_key_bytes); + + log!("Stored Pubkey"); + *self.state.write().await = AuthState::Identified { omikron_id }; - // Encrypt challenge - let encrypted = encrypt(get_private_key(), omikron_pub_key, &challenge) - .map_err(|_| OmikronError::AuthenticationFailed)?; + log!("Stored State"); + + let challenge_clone = challenge.clone(); + let private_key = get_private_key(); + let public_key_for_encrypt = omikron_pub_key; + + let encrypted = tokio::task::spawn_blocking(move || { + encrypt(private_key, public_key_for_encrypt, &challenge_clone) + .map_err(|_| OmikronError::AuthenticationFailed) + }) + .await + .map_err(|_| OmikronError::AuthenticationFailed)??; + + log!("Encrypted Challenge"); // Send challenge response let response = CommunicationValue::new(CommunicationType::challenge) @@ -268,6 +277,7 @@ impl OmikronConnection { ) .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + log!("Sending Challenge"); self.send(&response).await } @@ -1065,7 +1075,13 @@ impl OmikronConnection { self.send(&error).await } - pub async fn close(self: Arc) {} + pub async fn close(self: Arc) { + log_in!( + self.get_omikron_id().await.unwrap_or(0), + PrintType::Omega, + "Omikron connection Closed" + ); + } async fn cleanup(self: Arc) { if let Some(omikron_id) = self.state.read().await.omikron_id() { @@ -1101,11 +1117,6 @@ impl OmikronConnection { // ============================================================================ pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "cert.pem") - .map_err(|e| format!("Failed to load certificate: {}", e))?; - let key_pem = load_file_vec("certs", "key.pem") - .map_err(|e| format!("Failed to load private key: {}", e))?; - let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); @@ -1113,10 +1124,10 @@ pub async fn start(port: u16) -> Result<(), Box> { let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); - while let Some((sender, receiver)) = host.next().await { + while let Some((sender, mut receiver)) = host.next().await { tokio::spawn(async move { let conn = OmikronConnection::new(sender); - conn.handle(receiver).await; + conn.handle(&mut receiver).await; }); } diff --git a/src/util/logger.rs b/src/util/logger.rs index e861e69..d1a7764 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,5 @@ use std::{ - collections::HashMap, + collections::{BTreeMap, HashMap}, fs::{self, OpenOptions}, io::Write, path::Path, @@ -249,7 +249,7 @@ pub fn format_cv(cv: &CommunicationValue) -> String { let comm_type = cv.get_type().to_string(); parts.push(format!("{}", comm_type)); - let data: &HashMap = cv.get_data_container(); + let data: &BTreeMap = cv.get_data_container(); let formated_data = format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); From 4a819b66998fb9d2e603def0763fd61bc6b48c7a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:10:55 +0100 Subject: [PATCH 091/220] Cleanup --- Cargo.lock | 620 ++--------------------------------------------------- Cargo.toml | 26 +-- 2 files changed, 18 insertions(+), 628 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a353f19..0ca5d3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,79 +6,35 @@ version = 4 name = "Omega" version = "0.1.0" dependencies = [ - "actix", - "actix-rt", "actix-web", - "actix-web-actors", "aes-gcm", "ansi_term", "anyhow", - "async-trait", "base64", - "bytes", - "color-eyre", "dashmap", "dotenv", "epsilon-core", "epsilon-native", - "futures", - "futures-util", "hex", "hkdf", - "http-body-util", "json", "once_cell", - "pnet", - "quinn", "rand 0.8.5", "rand_core 0.6.4", "reqwest", "rustls", "rustls-pemfile", - "sha1", "sha2", "sqlx", "strum 0.27.2", "strum_macros 0.27.2", - "sysinfo", "thiserror 2.0.18", "tokio", - "tokio-rustls", - "tokio-tungstenite", - "tokio-util", - "tower", - "tungstenite", "uuid", - "walkdir", "x448", "zip", ] -[[package]] -name = "actix" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" -dependencies = [ - "actix-macros", - "actix-rt", - "actix_derive", - "bitflags 2.11.0", - "bytes", - "crossbeam-channel", - "futures-core", - "futures-sink", - "futures-task", - "futures-util", - "log", - "once_cell", - "parking_lot", - "pin-project-lite", - "smallvec", - "tokio", - "tokio-util", -] - [[package]] name = "actix-codec" version = "0.5.2" @@ -167,7 +123,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" dependencies = [ - "actix-macros", "futures-core", "tokio", ] @@ -272,24 +227,6 @@ dependencies = [ "url", ] -[[package]] -name = "actix-web-actors" -version = "4.3.1+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" -dependencies = [ - "actix", - "actix-codec", - "actix-http", - "actix-web", - "bytes", - "bytestring", - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - [[package]] name = "actix-web-codegen" version = "4.3.0" @@ -302,26 +239,6 @@ dependencies = [ "syn", ] -[[package]] -name = "actix_derive" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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" @@ -598,17 +515,6 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atoi" version = "2.0.0" @@ -653,21 +559,6 @@ dependencies = [ "fs_extra", ] -[[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.1", -] - [[package]] name = "base64" version = "0.22.1" @@ -823,33 +714,6 @@ dependencies = [ "cc", ] -[[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "combine" version = "4.6.7" @@ -960,15 +824,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -1171,10 +1026,9 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" +source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" dependencies = [ "byteorder", - "quinn", "rand 0.8.5", "strum 0.28.0", "strum_macros 0.28.0", @@ -1183,21 +1037,15 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" +source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" dependencies = [ - "anyhow", - "async-trait", - "aws-lc-rs", - "bytes", "epsilon-core", "quinn", - "rcgen", "rustls", "rustls-native-certs", "thiserror 2.0.18", "tokio", "wtransport", - "wtransport-proto", ] [[package]] @@ -1254,28 +1102,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand 0.9.2", - "siphasher", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1352,21 +1178,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.32" @@ -1439,17 +1250,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.32" @@ -1468,10 +1268,8 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "futures-channel", "futures-core", "futures-io", - "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1539,18 +1337,6 @@ dependencies = [ "polyval", ] -[[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 = "gloo-timers" version = "0.3.0" @@ -1923,12 +1709,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - [[package]] name = "indexmap" version = "2.13.0" @@ -1976,15 +1756,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "ipnetwork" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" -dependencies = [ - "serde", -] - [[package]] name = "iri-string" version = "0.7.10" @@ -2087,9 +1858,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" @@ -2238,12 +2009,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - [[package]] name = "nom" version = "7.1.3" @@ -2254,15 +2019,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "ntapi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -2325,34 +2081,6 @@ dependencies = [ "libm", ] -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "objc2-io-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" -dependencies = [ - "libc", - "objc2-core-foundation", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "octets" version = "0.3.5" @@ -2386,12 +2114,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "owo-colors" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" - [[package]] name = "parking" version = "2.2.1" @@ -2418,7 +2140,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2512,97 +2234,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "pnet" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" -dependencies = [ - "ipnetwork", - "pnet_base", - "pnet_datalink", - "pnet_packet", - "pnet_sys", - "pnet_transport", -] - -[[package]] -name = "pnet_base" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" -dependencies = [ - "no-std-net", -] - -[[package]] -name = "pnet_datalink" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" -dependencies = [ - "ipnetwork", - "libc", - "pnet_base", - "pnet_sys", - "winapi", -] - -[[package]] -name = "pnet_macros" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn", -] - -[[package]] -name = "pnet_macros_support" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" -dependencies = [ - "pnet_base", -] - -[[package]] -name = "pnet_packet" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" -dependencies = [ - "glob", - "pnet_base", - "pnet_macros", - "pnet_macros_support", -] - -[[package]] -name = "pnet_sys" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "pnet_transport" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" -dependencies = [ - "libc", - "pnet_base", - "pnet_packet", - "pnet_sys", -] - [[package]] name = "polling" version = "2.8.0" @@ -2722,7 +2353,6 @@ checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "aws-lc-rs", "bytes", - "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", @@ -2730,7 +2360,6 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", - "rustls-platform-verifier", "slab", "thiserror 2.0.18", "tinyvec", @@ -2845,8 +2474,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ "aws-lc-rs", - "pem", - "ring", "rustls-pki-types", "time", "x509-parser 0.18.1", @@ -2978,12 +2605,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -3263,15 +2884,6 @@ dependencies = [ "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" @@ -3304,12 +2916,6 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - [[package]] name = "slab" version = "0.4.12" @@ -3652,20 +3258,6 @@ dependencies = [ "syn", ] -[[package]] -name = "sysinfo" -version = "0.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" -dependencies = [ - "libc", - "memchr", - "ntapi", - "objc2-core-foundation", - "objc2-io-kit", - "windows", -] - [[package]] name = "system-configuration" version = "0.7.0" @@ -3727,15 +3319,6 @@ dependencies = [ "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 = "time" version = "0.3.47" @@ -3830,18 +3413,6 @@ dependencies = [ "tokio", ] -[[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", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -3850,12 +3421,8 @@ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", - "futures-io", "futures-sink", - "futures-util", - "hashbrown 0.15.5", "pin-project-lite", - "slab", "tokio", ] @@ -3934,28 +3501,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", ] [[package]] @@ -3964,23 +3509,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" -dependencies = [ - "bytes", - "data-encoding", - "http 1.4.0", - "httparse", - "log", - "rand 0.9.2", - "sha1", - "thiserror 2.0.18", - "utf-8", -] - [[package]] name = "typenum" version = "1.19.0" @@ -4060,12 +3588,6 @@ dependencies = [ "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" @@ -4083,12 +3605,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "value-bag" version = "1.12.0" @@ -4325,114 +3841,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - [[package]] name = "windows-registry" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[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", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -4441,16 +3864,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[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", + "windows-link", ] [[package]] @@ -4459,7 +3873,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4504,7 +3918,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4559,7 +3973,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -4570,15 +3984,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4930,7 +4335,6 @@ dependencies = [ "lazy_static", "nom", "oid-registry", - "ring", "rusticata-macros", "thiserror 2.0.18", "time", diff --git a/Cargo.toml b/Cargo.toml index a5cb77f..34673fb 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,47 +7,33 @@ edition = "2024" epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } -actix = "0.13.5" -actix-rt = "2.11.0" actix-web = { version = "4.12.1", features = ["rustls-0_23"] } -actix-web-actors = "4.3.1" aes-gcm = "*" ansi_term = "0.12.1" anyhow = "1.0.101" -async-trait = "0.1.89" base64 = "0.22.1" -bytes = "1.11.1" -color-eyre = "0.6.5" dashmap = "6.1.0" dotenv = "0.15.0" -futures = "0.3.31" -futures-util = "0.3.31" hex = "0.4.3" hkdf = "0.12.4" -http-body-util = "0.1.3" json = "0.12.4" once_cell = "1.21.3" -pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } reqwest = { version = "0.13.2" } -rustls = { version = "0.23.37", default-features = false, features = ["std", "tls12", "aws-lc-rs", "prefer-post-quantum"] } -quinn = { version = "0.11.9", default-features = false, features = ["runtime-tokio", "rustls-aws-lc-rs"] } +rustls = { version = "0.23.37", default-features = false, features = [ + "std", + "tls12", + "aws-lc-rs", + "prefer-post-quantum", +] } rustls-pemfile = "2.2.0" -sha1 = "0.10.6" sha2 = "0.10.9" sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } strum = "0.27.2" strum_macros = "0.27.2" -sysinfo = "0.37.2" tokio = { version = "*", features = ["full"] } -tokio-rustls = "0.26.4" -tokio-tungstenite = "0.28.0" -tokio-util = { version = "*", features = ["full"] } -tower = "0.5.2" -tungstenite = "0.28.0" uuid = { version = "1.19.0", features = ["v4"] } -walkdir = "2.5.0" x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" From 4b5a9fdf7dda8daaa934103f6f54dae269615ce3 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 8 Mar 2026 18:10:55 +0100 Subject: [PATCH 092/220] Cleanup --- Cargo.lock | 620 ++--------------------------------------------------- Cargo.toml | 26 +-- 2 files changed, 18 insertions(+), 628 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a353f19..0ca5d3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,79 +6,35 @@ version = 4 name = "Omega" version = "0.1.0" dependencies = [ - "actix", - "actix-rt", "actix-web", - "actix-web-actors", "aes-gcm", "ansi_term", "anyhow", - "async-trait", "base64", - "bytes", - "color-eyre", "dashmap", "dotenv", "epsilon-core", "epsilon-native", - "futures", - "futures-util", "hex", "hkdf", - "http-body-util", "json", "once_cell", - "pnet", - "quinn", "rand 0.8.5", "rand_core 0.6.4", "reqwest", "rustls", "rustls-pemfile", - "sha1", "sha2", "sqlx", "strum 0.27.2", "strum_macros 0.27.2", - "sysinfo", "thiserror 2.0.18", "tokio", - "tokio-rustls", - "tokio-tungstenite", - "tokio-util", - "tower", - "tungstenite", "uuid", - "walkdir", "x448", "zip", ] -[[package]] -name = "actix" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de7fa236829ba0841304542f7614c42b80fca007455315c45c785ccfa873a85b" -dependencies = [ - "actix-macros", - "actix-rt", - "actix_derive", - "bitflags 2.11.0", - "bytes", - "crossbeam-channel", - "futures-core", - "futures-sink", - "futures-task", - "futures-util", - "log", - "once_cell", - "parking_lot", - "pin-project-lite", - "smallvec", - "tokio", - "tokio-util", -] - [[package]] name = "actix-codec" version = "0.5.2" @@ -167,7 +123,6 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" dependencies = [ - "actix-macros", "futures-core", "tokio", ] @@ -272,24 +227,6 @@ dependencies = [ "url", ] -[[package]] -name = "actix-web-actors" -version = "4.3.1+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980" -dependencies = [ - "actix", - "actix-codec", - "actix-http", - "actix-web", - "bytes", - "bytestring", - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - [[package]] name = "actix-web-codegen" version = "4.3.0" @@ -302,26 +239,6 @@ dependencies = [ "syn", ] -[[package]] -name = "actix_derive" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6ac1e58cded18cb28ddc17143c4dea5345b3ad575e14f32f66e4054a56eb271" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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" @@ -598,17 +515,6 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" -[[package]] -name = "async-trait" -version = "0.1.89" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "atoi" version = "2.0.0" @@ -653,21 +559,6 @@ dependencies = [ "fs_extra", ] -[[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.1", -] - [[package]] name = "base64" version = "0.22.1" @@ -823,33 +714,6 @@ dependencies = [ "cc", ] -[[package]] -name = "color-eyre" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" -dependencies = [ - "backtrace", - "color-spantrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", - "tracing-error", -] - -[[package]] -name = "color-spantrace" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" -dependencies = [ - "once_cell", - "owo-colors", - "tracing-core", - "tracing-error", -] - [[package]] name = "combine" version = "4.6.7" @@ -960,15 +824,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -1171,10 +1026,9 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" +source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" dependencies = [ "byteorder", - "quinn", "rand 0.8.5", "strum 0.28.0", "strum_macros 0.28.0", @@ -1183,21 +1037,15 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#a175705a731a124bca7507ea897d29f536bb15dc" +source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" dependencies = [ - "anyhow", - "async-trait", - "aws-lc-rs", - "bytes", "epsilon-core", "quinn", - "rcgen", "rustls", "rustls-native-certs", "thiserror 2.0.18", "tokio", "wtransport", - "wtransport-proto", ] [[package]] @@ -1254,28 +1102,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "fastbloom" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7f34442dbe69c60fe8eaf58a8cafff81a1f278816d8ab4db255b3bef4ac3c4" -dependencies = [ - "getrandom 0.3.4", - "libm", - "rand 0.9.2", - "siphasher", -] - [[package]] name = "fastrand" version = "1.9.0" @@ -1352,21 +1178,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "futures" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.32" @@ -1439,17 +1250,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "futures-sink" version = "0.3.32" @@ -1468,10 +1268,8 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ - "futures-channel", "futures-core", "futures-io", - "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1539,18 +1337,6 @@ dependencies = [ "polyval", ] -[[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 = "gloo-timers" version = "0.3.0" @@ -1923,12 +1709,6 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" -[[package]] -name = "indenter" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" - [[package]] name = "indexmap" version = "2.13.0" @@ -1976,15 +1756,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "ipnetwork" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" -dependencies = [ - "serde", -] - [[package]] name = "iri-string" version = "0.7.10" @@ -2087,9 +1858,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libm" @@ -2238,12 +2009,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "no-std-net" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" - [[package]] name = "nom" version = "7.1.3" @@ -2254,15 +2019,6 @@ dependencies = [ "minimal-lexical", ] -[[package]] -name = "ntapi" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" -dependencies = [ - "winapi", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -2325,34 +2081,6 @@ dependencies = [ "libm", ] -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "objc2-io-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" -dependencies = [ - "libc", - "objc2-core-foundation", -] - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "memchr", -] - [[package]] name = "octets" version = "0.3.5" @@ -2386,12 +2114,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" -[[package]] -name = "owo-colors" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" - [[package]] name = "parking" version = "2.2.1" @@ -2418,7 +2140,7 @@ dependencies = [ "libc", "redox_syscall 0.5.18", "smallvec", - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -2512,97 +2234,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "pnet" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "682396b533413cc2e009fbb48aadf93619a149d3e57defba19ff50ce0201bd0d" -dependencies = [ - "ipnetwork", - "pnet_base", - "pnet_datalink", - "pnet_packet", - "pnet_sys", - "pnet_transport", -] - -[[package]] -name = "pnet_base" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" -dependencies = [ - "no-std-net", -] - -[[package]] -name = "pnet_datalink" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" -dependencies = [ - "ipnetwork", - "libc", - "pnet_base", - "pnet_sys", - "winapi", -] - -[[package]] -name = "pnet_macros" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13325ac86ee1a80a480b0bc8e3d30c25d133616112bb16e86f712dcf8a71c863" -dependencies = [ - "proc-macro2", - "quote", - "regex", - "syn", -] - -[[package]] -name = "pnet_macros_support" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed67a952585d509dd0003049b1fc56b982ac665c8299b124b90ea2bdb3134ab" -dependencies = [ - "pnet_base", -] - -[[package]] -name = "pnet_packet" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c96ebadfab635fcc23036ba30a7d33a80c39e8461b8bd7dc7bb186acb96560f" -dependencies = [ - "glob", - "pnet_base", - "pnet_macros", - "pnet_macros_support", -] - -[[package]] -name = "pnet_sys" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "pnet_transport" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f604d98bc2a6591cf719b58d3203fd882bdd6bf1db696c4ac97978e9f4776bf" -dependencies = [ - "libc", - "pnet_base", - "pnet_packet", - "pnet_sys", -] - [[package]] name = "polling" version = "2.8.0" @@ -2722,7 +2353,6 @@ checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "aws-lc-rs", "bytes", - "fastbloom", "getrandom 0.3.4", "lru-slab", "rand 0.9.2", @@ -2730,7 +2360,6 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", - "rustls-platform-verifier", "slab", "thiserror 2.0.18", "tinyvec", @@ -2845,8 +2474,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" dependencies = [ "aws-lc-rs", - "pem", - "ring", "rustls-pki-types", "time", "x509-parser 0.18.1", @@ -2978,12 +2605,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustc-demangle" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -3263,15 +2884,6 @@ dependencies = [ "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" @@ -3304,12 +2916,6 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" -[[package]] -name = "siphasher" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" - [[package]] name = "slab" version = "0.4.12" @@ -3652,20 +3258,6 @@ dependencies = [ "syn", ] -[[package]] -name = "sysinfo" -version = "0.37.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" -dependencies = [ - "libc", - "memchr", - "ntapi", - "objc2-core-foundation", - "objc2-io-kit", - "windows", -] - [[package]] name = "system-configuration" version = "0.7.0" @@ -3727,15 +3319,6 @@ dependencies = [ "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 = "time" version = "0.3.47" @@ -3830,18 +3413,6 @@ dependencies = [ "tokio", ] -[[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", -] - [[package]] name = "tokio-util" version = "0.7.18" @@ -3850,12 +3421,8 @@ checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", - "futures-io", "futures-sink", - "futures-util", - "hashbrown 0.15.5", "pin-project-lite", - "slab", "tokio", ] @@ -3934,28 +3501,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-error" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" -dependencies = [ - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" -dependencies = [ - "sharded-slab", - "thread_local", - "tracing-core", ] [[package]] @@ -3964,23 +3509,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" -dependencies = [ - "bytes", - "data-encoding", - "http 1.4.0", - "httparse", - "log", - "rand 0.9.2", - "sha1", - "thiserror 2.0.18", - "utf-8", -] - [[package]] name = "typenum" version = "1.19.0" @@ -4060,12 +3588,6 @@ dependencies = [ "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" @@ -4083,12 +3605,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - [[package]] name = "value-bag" version = "1.12.0" @@ -4325,114 +3841,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.61.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-link 0.1.3", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" -dependencies = [ - "windows-core", -] - -[[package]] -name = "windows-core" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link 0.1.3", - "windows-result 0.3.4", - "windows-strings 0.4.2", -] - -[[package]] -name = "windows-future" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" -dependencies = [ - "windows-core", - "windows-link 0.1.3", - "windows-threading", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -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.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" -dependencies = [ - "windows-core", - "windows-link 0.1.3", -] - [[package]] name = "windows-registry" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-link 0.2.1", - "windows-result 0.4.1", - "windows-strings 0.5.1", -] - -[[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", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] @@ -4441,16 +3864,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link 0.2.1", -] - -[[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", + "windows-link", ] [[package]] @@ -4459,7 +3873,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4504,7 +3918,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -4559,7 +3973,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm 0.53.1", "windows_aarch64_msvc 0.53.1", "windows_i686_gnu 0.53.1", @@ -4570,15 +3984,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows-threading" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -4930,7 +4335,6 @@ dependencies = [ "lazy_static", "nom", "oid-registry", - "ring", "rusticata-macros", "thiserror 2.0.18", "time", diff --git a/Cargo.toml b/Cargo.toml index a5cb77f..34673fb 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,47 +7,33 @@ edition = "2024" epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } -actix = "0.13.5" -actix-rt = "2.11.0" actix-web = { version = "4.12.1", features = ["rustls-0_23"] } -actix-web-actors = "4.3.1" aes-gcm = "*" ansi_term = "0.12.1" anyhow = "1.0.101" -async-trait = "0.1.89" base64 = "0.22.1" -bytes = "1.11.1" -color-eyre = "0.6.5" dashmap = "6.1.0" dotenv = "0.15.0" -futures = "0.3.31" -futures-util = "0.3.31" hex = "0.4.3" hkdf = "0.12.4" -http-body-util = "0.1.3" json = "0.12.4" once_cell = "1.21.3" -pnet = "0.35.0" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } reqwest = { version = "0.13.2" } -rustls = { version = "0.23.37", default-features = false, features = ["std", "tls12", "aws-lc-rs", "prefer-post-quantum"] } -quinn = { version = "0.11.9", default-features = false, features = ["runtime-tokio", "rustls-aws-lc-rs"] } +rustls = { version = "0.23.37", default-features = false, features = [ + "std", + "tls12", + "aws-lc-rs", + "prefer-post-quantum", +] } rustls-pemfile = "2.2.0" -sha1 = "0.10.6" sha2 = "0.10.9" sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } strum = "0.27.2" strum_macros = "0.27.2" -sysinfo = "0.37.2" tokio = { version = "*", features = ["full"] } -tokio-rustls = "0.26.4" -tokio-tungstenite = "0.28.0" -tokio-util = { version = "*", features = ["full"] } -tower = "0.5.2" -tungstenite = "0.28.0" uuid = { version = "1.19.0", features = ["v4"] } -walkdir = "2.5.0" x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" From 59d81a1fd667fc2a01afa211553bde85681a1834 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:30:15 +0100 Subject: [PATCH 093/220] [fix] no longer logging ping pong --- Cargo.lock | 17 +++++++++-------- src/server/omikron_connection.rs | 9 ++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ca5d3b..2f70fe0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1026,8 +1026,9 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" +source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" dependencies = [ + "base64", "byteorder", "rand 0.8.5", "strum 0.28.0", @@ -1037,7 +1038,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" +source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" dependencies = [ "epsilon-core", "quinn", @@ -2347,9 +2348,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "aws-lc-rs", "bytes", @@ -4374,18 +4375,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 5b4ddc3..ec86bdf 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -171,8 +171,9 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Log incoming - log_cv_in!(PrintType::Omikron, &cv); + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_in!(PrintType::Omikron, &cv); + } let msg_id = cv.get_id(); @@ -1055,7 +1056,9 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - log_cv_out!(PrintType::Omikron, cv); + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Omikron, cv); + } let guard = self.sender.lock().await; let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; From 16006dc26e1b3748fd2159855570fd6fca5bbb17 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 9 Mar 2026 22:30:15 +0100 Subject: [PATCH 094/220] [fix] no longer logging ping pong --- Cargo.lock | 17 +++++++++-------- src/server/omikron_connection.rs | 9 ++++++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0ca5d3b..2f70fe0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1026,8 +1026,9 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" +source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" dependencies = [ + "base64", "byteorder", "rand 0.8.5", "strum 0.28.0", @@ -1037,7 +1038,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#05a7b22b2e05a9b023b0d976d65dddf06edccd7c" +source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" dependencies = [ "epsilon-core", "quinn", @@ -2347,9 +2348,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.13" +version = "0.11.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" dependencies = [ "aws-lc-rs", "bytes", @@ -4374,18 +4375,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", diff --git a/src/server/omikron_connection.rs b/src/server/omikron_connection.rs index 5b4ddc3..ec86bdf 100644 --- a/src/server/omikron_connection.rs +++ b/src/server/omikron_connection.rs @@ -171,8 +171,9 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Log incoming - log_cv_in!(PrintType::Omikron, &cv); + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_in!(PrintType::Omikron, &cv); + } let msg_id = cv.get_id(); @@ -1055,7 +1056,9 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - log_cv_out!(PrintType::Omikron, cv); + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Omikron, cv); + } let guard = self.sender.lock().await; let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; From e6fd8512416bf5b5162b9400ea2e54b1f21af584 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:30:01 +0100 Subject: [PATCH 095/220] [Fix] API --- Cargo.lock | 12 ++++++------ src/main.rs | 3 ++- src/server/api.rs | 9 +-------- src/server/mod.rs | 2 -- src/server/server.rs | 10 +++------- src/transport/mod.rs | 2 ++ src/{server => transport}/omikron_connection.rs | 7 ++++--- src/{server => transport}/omikron_manager.rs | 2 +- 8 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 src/transport/mod.rs rename src/{server => transport}/omikron_connection.rs (99%) rename src/{server => transport}/omikron_manager.rs (94%) diff --git a/Cargo.lock b/Cargo.lock index 2f70fe0..068a32e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1026,7 +1026,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" +source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" dependencies = [ "base64", "byteorder", @@ -1038,7 +1038,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" +source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" dependencies = [ "epsilon-core", "quinn", @@ -2099,9 +2099,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -2766,9 +2766,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] diff --git a/src/main.rs b/src/main.rs index 866d216..8143c9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ mod server; mod sql; +mod transport; mod util; -use crate::server::omikron_connection; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; +use crate::transport::omikron_connection; use crate::util::crypto_helper::load_public_key; use crate::util::crypto_helper::load_secret_key; use crate::util::logger::PrintType; diff --git a/src/server/api.rs b/src/server/api.rs index b216112..61dc86e 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,7 +1,7 @@ use crate::get_public_key; -use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; +use crate::transport::omikron_manager::get_random_omikron; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, @@ -272,10 +272,3 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) .body(body_bytes) } - -pub fn bad_request() -> (StatusCode, String) { - (StatusCode::BAD_REQUEST, "400 Bad Request".to_string()) -} -pub fn not_found() -> (StatusCode, String) { - (StatusCode::NOT_FOUND, "404 Not Found".to_string()) -} diff --git a/src/server/mod.rs b/src/server/mod.rs index aab6ab6..8645e6e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,3 @@ pub mod api; -pub mod omikron_connection; -pub mod omikron_manager; pub mod server; pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs index c1b0e10..3618eb7 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,7 +1,7 @@ use crate::{ log, server::{api, short_link::get_short_link}, - util::file_util::get_directory, + util::file_util::load_file_buf, }; use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; @@ -10,14 +10,10 @@ use rustls::ServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls_pemfile::{certs, pkcs8_private_keys}; -use std::fs::File; -use std::io::BufReader; - pub async fn start(port: u16) -> anyhow::Result<()> { - let mut cert_reader = - BufReader::new(File::open(format!("{}/certs/cert.pem", get_directory()))?); + let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; - let mut key_reader = BufReader::new(File::open(format!("{}/certs/key.pem", get_directory()))?); + let mut key_reader = load_file_buf("certs", "server_key.pem")?; let cert_chain: Vec> = certs(&mut cert_reader).collect::>()?; diff --git a/src/transport/mod.rs b/src/transport/mod.rs new file mode 100644 index 0000000..4f3953a --- /dev/null +++ b/src/transport/mod.rs @@ -0,0 +1,2 @@ +pub mod omikron_connection; +pub mod omikron_manager; diff --git a/src/server/omikron_connection.rs b/src/transport/omikron_connection.rs similarity index 99% rename from src/server/omikron_connection.rs rename to src/transport/omikron_connection.rs index ec86bdf..504d723 100644 --- a/src/server/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,11 +1,12 @@ use crate::{ get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, - server::{omikron_manager, short_link::add_short_link}, + server::short_link::add_short_link, sql::{ connection_status::UserStatus, sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, user_online_tracker::{self}, }, + transport::omikron_manager, util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; @@ -1120,9 +1121,9 @@ impl OmikronConnection { // ============================================================================ pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); + let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); + let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); diff --git a/src/server/omikron_manager.rs b/src/transport/omikron_manager.rs similarity index 94% rename from src/server/omikron_manager.rs rename to src/transport/omikron_manager.rs index 7556674..fbee4a9 100644 --- a/src/server/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,4 +1,4 @@ -use crate::server::omikron_connection::OmikronConnection; +use crate::transport::omikron_connection::OmikronConnection; use dashmap::DashMap; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; From 17da3ec0a37b42037db2e792fe218860539d3aad Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 12 Mar 2026 17:30:01 +0100 Subject: [PATCH 096/220] [Fix] API --- Cargo.lock | 12 ++++++------ src/main.rs | 3 ++- src/server/api.rs | 9 +-------- src/server/mod.rs | 2 -- src/server/server.rs | 10 +++------- src/transport/mod.rs | 2 ++ src/{server => transport}/omikron_connection.rs | 7 ++++--- src/{server => transport}/omikron_manager.rs | 2 +- 8 files changed, 19 insertions(+), 28 deletions(-) create mode 100644 src/transport/mod.rs rename src/{server => transport}/omikron_connection.rs (99%) rename src/{server => transport}/omikron_manager.rs (94%) diff --git a/Cargo.lock b/Cargo.lock index 2f70fe0..068a32e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1026,7 +1026,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" +source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" dependencies = [ "base64", "byteorder", @@ -1038,7 +1038,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#809a32581843e663d35d4b3222f2cc59371d2786" +source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" dependencies = [ "epsilon-core", "quinn", @@ -2099,9 +2099,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" @@ -2766,9 +2766,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" dependencies = [ "windows-sys 0.61.2", ] diff --git a/src/main.rs b/src/main.rs index 866d216..8143c9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,11 @@ mod server; mod sql; +mod transport; mod util; -use crate::server::omikron_connection; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; +use crate::transport::omikron_connection; use crate::util::crypto_helper::load_public_key; use crate::util::crypto_helper::load_secret_key; use crate::util::logger::PrintType; diff --git a/src/server/api.rs b/src/server/api.rs index b216112..61dc86e 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,7 +1,7 @@ use crate::get_public_key; -use crate::server::omikron_manager::get_random_omikron; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; +use crate::transport::omikron_manager::get_random_omikron; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, @@ -272,10 +272,3 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) .body(body_bytes) } - -pub fn bad_request() -> (StatusCode, String) { - (StatusCode::BAD_REQUEST, "400 Bad Request".to_string()) -} -pub fn not_found() -> (StatusCode, String) { - (StatusCode::NOT_FOUND, "404 Not Found".to_string()) -} diff --git a/src/server/mod.rs b/src/server/mod.rs index aab6ab6..8645e6e 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,3 @@ pub mod api; -pub mod omikron_connection; -pub mod omikron_manager; pub mod server; pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs index c1b0e10..3618eb7 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,7 +1,7 @@ use crate::{ log, server::{api, short_link::get_short_link}, - util::file_util::get_directory, + util::file_util::load_file_buf, }; use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; @@ -10,14 +10,10 @@ use rustls::ServerConfig; use rustls::pki_types::{CertificateDer, PrivateKeyDer}; use rustls_pemfile::{certs, pkcs8_private_keys}; -use std::fs::File; -use std::io::BufReader; - pub async fn start(port: u16) -> anyhow::Result<()> { - let mut cert_reader = - BufReader::new(File::open(format!("{}/certs/cert.pem", get_directory()))?); + let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; - let mut key_reader = BufReader::new(File::open(format!("{}/certs/key.pem", get_directory()))?); + let mut key_reader = load_file_buf("certs", "server_key.pem")?; let cert_chain: Vec> = certs(&mut cert_reader).collect::>()?; diff --git a/src/transport/mod.rs b/src/transport/mod.rs new file mode 100644 index 0000000..4f3953a --- /dev/null +++ b/src/transport/mod.rs @@ -0,0 +1,2 @@ +pub mod omikron_connection; +pub mod omikron_manager; diff --git a/src/server/omikron_connection.rs b/src/transport/omikron_connection.rs similarity index 99% rename from src/server/omikron_connection.rs rename to src/transport/omikron_connection.rs index ec86bdf..504d723 100644 --- a/src/server/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,11 +1,12 @@ use crate::{ get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, - server::{omikron_manager, short_link::add_short_link}, + server::short_link::add_short_link, sql::{ connection_status::UserStatus, sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, user_online_tracker::{self}, }, + transport::omikron_manager, util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; @@ -1120,9 +1121,9 @@ impl OmikronConnection { // ============================================================================ pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); + let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); + let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); diff --git a/src/server/omikron_manager.rs b/src/transport/omikron_manager.rs similarity index 94% rename from src/server/omikron_manager.rs rename to src/transport/omikron_manager.rs index 7556674..fbee4a9 100644 --- a/src/server/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,4 +1,4 @@ -use crate::server::omikron_connection::OmikronConnection; +use crate::transport::omikron_connection::OmikronConnection; use dashmap::DashMap; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; From 686e67cd05a6c9a54a640ca29155a358f503d7cf Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:12:34 +0100 Subject: [PATCH 097/220] [Fix] Forgor to deimplement dynamic downloads --- Cargo.lock | 12 ++++++------ src/server/api.rs | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 068a32e..1444c40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -1026,7 +1026,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" dependencies = [ "base64", "byteorder", @@ -1038,7 +1038,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" dependencies = [ "epsilon-core", "quinn", @@ -3363,9 +3363,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] diff --git a/src/server/api.rs b/src/server/api.rs index 61dc86e..eb6f9cf 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -2,6 +2,7 @@ use crate::get_public_key; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; +use crate::util::file_util::get_directory; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, @@ -37,7 +38,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { // DOWNLOAD IOTA FRONTEND // ================================================== ["api", "download", "iota_frontend"] => { - let file_path = "downloads/[iota_frontend].zip"; + let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); match std::fs::read(file_path) { Ok(file_bytes) => { From d0d688db1f161bc4c51c9155124c17937e5e47e5 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:12:34 +0100 Subject: [PATCH 098/220] [Fix] Forgor to deimplement dynamic downloads --- Cargo.lock | 12 ++++++------ src/server/api.rs | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 068a32e..1444c40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.56" +version = "1.2.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" dependencies = [ "find-msvc-tools", "jobserver", @@ -1026,7 +1026,7 @@ dependencies = [ [[package]] name = "epsilon-core" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" dependencies = [ "base64", "byteorder", @@ -1038,7 +1038,7 @@ dependencies = [ [[package]] name = "epsilon-native" version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#42bee9c93b3669c872cc54dc5d031a7830551922" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" dependencies = [ "epsilon-core", "quinn", @@ -3363,9 +3363,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] diff --git a/src/server/api.rs b/src/server/api.rs index 61dc86e..eb6f9cf 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -2,6 +2,7 @@ use crate::get_public_key; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; +use crate::util::file_util::get_directory; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, util::crypto_helper::public_key_to_base64, @@ -37,7 +38,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { // DOWNLOAD IOTA FRONTEND // ================================================== ["api", "download", "iota_frontend"] => { - let file_path = "downloads/[iota_frontend].zip"; + let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); match std::fs::read(file_path) { Ok(file_bytes) => { From 234b816965836776b8115ee45d6740b1d9adc934 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 2 Apr 2026 22:25:10 +0200 Subject: [PATCH 099/220] Sync --- .gitignore | 16 +- Cargo.lock | 2527 ++++++----------- Cargo.toml | 38 +- README.md | 3 +- .../anonymous_client_connection.rs | 519 ++++ src/anonymous_clients/anonymous_manager.rs | 53 + src/anonymous_clients/mod.rs | 2 + src/calls/call_group.rs | 106 + src/calls/call_manager.rs | 97 + src/calls/call_util.rs | 151 + src/calls/caller.rs | 49 + src/calls/mod.rs | 4 + src/data/mod.rs | 1 + .../connection_status.rs => data/user.rs} | 0 src/main.rs | 56 +- src/omega/mod.rs | 1 + src/omega/omega_connection.rs | 741 +++++ src/omega/ping_pong_task.rs | 41 + src/rho/client_connection.rs | 583 ++++ src/rho/connection.rs | 358 +++ src/rho/iota_connection.rs | 461 +++ src/rho/mod.rs | 6 + src/rho/rho_connection.rs | 199 ++ src/rho/rho_manager.rs | 83 + src/rho/server.rs | 25 + src/server/api.rs | 275 -- src/server/mod.rs | 3 - src/server/server.rs | 67 - src/server/short_link.rs | 87 - src/sql/mod.rs | 3 - src/sql/sql.rs | 765 ----- src/sql/user_online_tracker.rs | 144 - src/transport/mod.rs | 2 - src/transport/omikron_connection.rs | 1139 -------- src/transport/omikron_manager.rs | 39 - src/util/crypto_helper.rs | 22 +- src/util/crypto_util.rs | 59 +- src/util/file_util.rs | 166 +- src/util/logger.rs | 100 +- 39 files changed, 4548 insertions(+), 4443 deletions(-) mode change 100755 => 100644 Cargo.toml create mode 100644 src/anonymous_clients/anonymous_client_connection.rs create mode 100644 src/anonymous_clients/anonymous_manager.rs create mode 100644 src/anonymous_clients/mod.rs create mode 100644 src/calls/call_group.rs create mode 100644 src/calls/call_manager.rs create mode 100644 src/calls/call_util.rs create mode 100644 src/calls/caller.rs create mode 100644 src/calls/mod.rs create mode 100644 src/data/mod.rs rename src/{sql/connection_status.rs => data/user.rs} (100%) create mode 100644 src/omega/mod.rs create mode 100644 src/omega/omega_connection.rs create mode 100644 src/omega/ping_pong_task.rs create mode 100644 src/rho/client_connection.rs create mode 100755 src/rho/connection.rs create mode 100755 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/rho/server.rs delete mode 100644 src/server/api.rs delete mode 100644 src/server/mod.rs delete mode 100644 src/server/server.rs delete mode 100644 src/server/short_link.rs delete mode 100644 src/sql/mod.rs delete mode 100644 src/sql/sql.rs delete mode 100644 src/sql/user_online_tracker.rs delete mode 100644 src/transport/mod.rs delete mode 100644 src/transport/omikron_connection.rs delete mode 100644 src/transport/omikron_manager.rs mode change 100644 => 100755 src/util/crypto_helper.rs diff --git a/.gitignore b/.gitignore index e3d273c..1baef92 100644 --- a/.gitignore +++ b/.gitignore @@ -8,22 +8,12 @@ target # MSVC Windows builds of rustc generate these, which store debugging information *.pdb - +*.env # 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/ - - -.env -/logs # Added by cargo - /target +/logs diff --git a/Cargo.lock b/Cargo.lock index 1444c40..41cef35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,248 +3,35 @@ version = 4 [[package]] -name = "Omega" +name = "Omikron" version = "0.1.0" dependencies = [ - "actix-web", "aes-gcm", "ansi_term", - "anyhow", - "base64", + "base64 0.22.1", "dashmap", "dotenv", - "epsilon-core", - "epsilon-native", + "futures", "hex", "hkdf", - "json", + "livekit-api", + "livekit-protocol", + "log", "once_cell", "rand 0.8.5", "rand_core 0.6.4", - "reqwest", "rustls", - "rustls-pemfile", "sha2", - "sqlx", - "strum 0.27.2", - "strum_macros 0.27.2", + "strum", + "strum_macros", "thiserror 2.0.18", "tokio", + "ttp-core", + "ttp-native", "uuid", "x448", - "zip", ] -[[package]] -name = "actix-codec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" -dependencies = [ - "bitflags 2.11.0", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "base64", - "bitflags 2.11.0", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "foldhash", - "futures-core", - "h2 0.3.27", - "http 0.2.12", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.9.2", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" -dependencies = [ - "bytestring", - "cfg-if", - "http 0.2.12", - "regex", - "regex-lite", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.10", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "impl-more", - "pin-project-lite", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-tls", - "actix-utils", - "actix-web-codegen", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "foldhash", - "futures-core", - "futures-util", - "impl-more", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "regex-lite", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.6.3", - "time", - "tracing", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" @@ -290,26 +77,14 @@ dependencies = [ ] [[package]] -name = "alloc-no-stdlib" -version = "2.0.4" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "alloc-no-stdlib", + "libc", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "ansi_term" version = "0.12.1" @@ -325,15 +100,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "asn1-rs" version = "0.7.1" @@ -404,8 +170,8 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", - "futures-lite 2.6.1", + "fastrand", + "futures-lite", "pin-project-lite", "slab", ] @@ -418,33 +184,13 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.5.0", "async-executor", - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "blocking", - "futures-lite 2.6.1", + "futures-lite", "once_cell", ] -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2 0.4.10", - "waker-fn", -] - [[package]] name = "async-io" version = "2.6.0" @@ -455,23 +201,14 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "parking", - "polling 3.11.0", - "rustix 1.1.4", + "polling", + "rustix", "slab", "windows-sys 0.61.2", ] -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - [[package]] name = "async-lock" version = "3.4.2" @@ -483,6 +220,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 1.0.69", + "url", +] + [[package]] name = "async-std" version = "1.13.2" @@ -491,13 +240,13 @@ checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" dependencies = [ "async-channel 1.9.0", "async-global-executor", - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "gloo-timers", "kv-log-macro", "log", @@ -516,12 +265,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] -name = "atoi" -version = "2.0.0" +name = "async-tungstenite" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" dependencies = [ - "num-traits", + "async-native-tls", + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.21.0", ] [[package]] @@ -538,9 +293,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -549,9 +304,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -559,6 +314,18 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -571,20 +338,11 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -dependencies = [ - "serde_core", -] [[package]] name = "block-buffer" @@ -595,6 +353,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "blocking" version = "1.6.2" @@ -604,31 +371,10 @@ dependencies = [ "async-channel 2.5.0", "async-task", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "piper", ] -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bumpalo" version = "3.20.2" @@ -647,29 +393,11 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "bytestring" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" -dependencies = [ - "bytes", -] - -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -677,12 +405,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -695,6 +417,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "num-traits", +] + [[package]] name = "cipher" version = "0.4.4" @@ -707,23 +438,13 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -739,42 +460,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[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" version = "0.10.1" @@ -800,45 +485,24 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -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 = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -859,6 +523,33 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -879,12 +570,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" -[[package]] -name = "deflate64" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" - [[package]] name = "der" version = "0.7.10" @@ -919,40 +604,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - [[package]] name = "digest" version = "0.10.7" @@ -965,6 +616,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -982,25 +643,57 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "ed448-goldilocks" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" dependencies = [ - "fiat-crypto", + "fiat-crypto 0.1.20", "hex", "subtle", ] @@ -1010,43 +703,26 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] [[package]] -name = "encoding_rs" -version = "0.8.35" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "cfg-if", -] - -[[package]] -name = "epsilon-core" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "base64", - "byteorder", - "rand 0.8.5", - "strum 0.28.0", - "strum_macros 0.28.0", -] - -[[package]] -name = "epsilon-native" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "epsilon-core", - "quinn", - "rustls", - "rustls-native-certs", - "thiserror 2.0.18", - "tokio", - "wtransport", + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", ] [[package]] @@ -1065,17 +741,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -1103,27 +768,34 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1131,26 +803,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "flate2" -version = "1.1.9" +name = "fixedbitset" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", - "zlib-rs", -] - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" @@ -1164,6 +820,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[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" @@ -1179,6 +850,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1206,51 +892,36 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -1269,8 +940,10 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1286,6 +959,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1351,41 +1025,14 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.27" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.4.0", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -1400,8 +1047,6 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", "foldhash", ] @@ -1412,13 +1057,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] -name = "hashlink" -version = "0.10.0" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" @@ -1426,12 +1068,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -1462,15 +1098,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "httlib-huffman" version = "0.3.4" @@ -1527,47 +1154,39 @@ 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.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2 0.4.13", "http 1.4.0", "http-body", "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", ] [[package]] -name = "hyper-rustls" -version = "0.27.7" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "http 1.4.0", + "bytes", + "http-body-util", "hyper", "hyper-util", - "rustls", - "rustls-pki-types", + "native-tls", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower-service", ] @@ -1577,7 +1196,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -1589,21 +1208,20 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.3", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1611,9 +1229,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1624,9 +1242,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1638,15 +1256,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1658,15 +1276,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1704,12 +1322,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "impl-more" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" - [[package]] name = "indexmap" version = "2.13.0" @@ -1731,26 +1343,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -1759,41 +1351,37 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", ] [[package]] -name = "itoa" -version = "1.0.17" +name = "itertools" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", + "either", ] [[package]] -name = "jni-sys" -version = "0.3.0" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" @@ -1807,19 +1395,36 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] [[package]] -name = "json" -version = "0.12.4" +name = "jsonwebtoken" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "base64 0.22.1", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac", + "js-sys", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde_json", + "sha2", + "signature", +] [[package]] name = "kv-log-macro" @@ -1830,12 +1435,6 @@ dependencies = [ "log", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.5.0" @@ -1851,17 +1450,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "libbz2-rs-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" - [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" @@ -1869,34 +1462,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "bitflags 2.11.0", - "libc", - "plain", - "redox_syscall 0.7.3", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1905,26 +1470,63 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] -name = "local-channel" -version = "0.1.5" +name = "livekit-api" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +checksum = "020c7eaab7cec16b69a058659f9acff32067e0e2d4f81e80639f3ffa29464b09" dependencies = [ - "futures-core", - "futures-sink", - "local-waker", + "async-tungstenite", + "base64 0.21.7", + "http 1.4.0", + "jsonwebtoken", + "livekit-protocol", + "log", + "os_info", + "parking_lot", + "pbjson-types", + "prost", + "rand 0.9.2", + "reqwest", + "scopeguard", + "serde", + "serde_json", + "sha2", + "thiserror 1.0.69", + "tokio-tungstenite", + "url", ] [[package]] -name = "local-waker" -version = "0.1.4" +name = "livekit-protocol" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" +checksum = "c21d56b4dc2598b1296ca891abb225bf1c05ea007d6f43bc4fd48c31be64bf59" +dependencies = [ + "futures-util", + "livekit-runtime", + "parking_lot", + "pbjson", + "pbjson-types", + "prost", + "serde", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "livekit-runtime" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532e84c6cdc5fe774f2b5d9912597b5f3bea561927a48296d03e24549d21c3f6" +dependencies = [ + "tokio", + "tokio-stream", +] [[package]] name = "lock_api" @@ -1950,38 +1552,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-rust2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" -dependencies = [ - "crc", - "sha2", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1989,25 +1565,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "mio" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ - "adler2", - "simd-adler32", + "libc", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "mio" -version = "1.1.1" +name = "multimap" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", - "wasi", - "windows-sys 0.61.2", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", ] [[package]] @@ -2048,9 +1648,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -2082,6 +1682,165 @@ dependencies = [ "libm", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "octets" version = "0.3.5" @@ -2109,12 +1868,90 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +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.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_info" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -2139,19 +1976,46 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] [[package]] -name = "pbkdf2" -version = "0.12.2" +name = "pbjson" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" dependencies = [ - "digest", - "hmac", + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck 0.4.1", + "itertools 0.11.0", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", ] [[package]] @@ -2160,7 +2024,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -2179,6 +2043,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2198,7 +2072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand", "futures-io", ] @@ -2229,28 +2103,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "polling" version = "3.11.0" @@ -2259,9 +2111,9 @@ checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.5.2", + "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -2279,9 +2131,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2292,12 +2144,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppmd-rust" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2317,6 +2163,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2326,6 +2181,59 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quinn" version = "0.11.9" @@ -2487,16 +2395,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -2522,12 +2421,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - [[package]] name = "regex-syntax" version = "0.8.10" @@ -2536,33 +2429,31 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.2" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", - "h2 0.4.13", "http 1.4.0", "http-body", "http-body-util", "hyper", - "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", - "mime", + "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", "rustls-pki-types", - "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower", "tower-http", "tower-service", @@ -2572,6 +2463,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2608,9 +2509,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -2630,30 +2531,16 @@ dependencies = [ "nom", ] -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -2685,15 +2572,6 @@ dependencies = [ "security-framework", ] -[[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.14.0" @@ -2704,38 +2582,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -2755,15 +2606,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[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.29" @@ -2779,14 +2621,28 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.10.1", + "bitflags", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -2911,12 +2767,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - [[package]] name = "slab" version = "0.4.12" @@ -2928,19 +2778,6 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] [[package]] name = "socket2" @@ -2967,9 +2804,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -2981,242 +2815,25 @@ dependencies = [ "der", ] -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "async-io 1.13.0", - "async-std", - "base64", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener 5.4.1", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror 2.0.18", - "tracing", - "url", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "async-std", - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bitflags 2.11.0", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bitflags 2.11.0", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.18", - "tracing", - "url", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" - [[package]] name = "strum" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "strum_macros" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -3260,24 +2877,16 @@ dependencies = [ ] [[package]] -name = "system-configuration" -version = "0.7.0" +name = "tempfile" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.9.4", - "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", + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -3353,9 +2962,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3405,28 +3014,40 @@ dependencies = [ ] [[package]] -name = "tokio-rustls" -version = "0.26.4" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "rustls", + "native-tls", "tokio", ] [[package]] -name = "tokio-util" -version = "0.7.18" +name = "tokio-stream" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ - "bytes", "futures-core", - "futures-sink", "pin-project-lite", "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.20.1", +] + [[package]] name = "tower" version = "0.5.3" @@ -3448,7 +3069,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags", "bytes", "futures-util", "http 1.4.0", @@ -3478,7 +3099,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3510,45 +3130,84 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttp-core" +version = "0.1.0" +source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" +dependencies = [ + "base64 0.22.1", + "byteorder", + "rand 0.8.5", + "strum", + "strum_macros", +] + +[[package]] +name = "ttp-native" +version = "0.1.0" +source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" +dependencies = [ + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "ttp-core", + "wtransport", +] + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -3589,6 +3248,12 @@ dependencies = [ "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" @@ -3597,9 +3262,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3624,22 +3289,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -3673,17 +3322,11 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -3694,23 +3337,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3718,9 +3357,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -3731,9 +3370,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -3766,7 +3405,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -3774,9 +3413,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -3792,25 +3431,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3827,15 +3447,6 @@ 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.2", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3848,53 +3459,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -3922,36 +3486,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3985,18 +3519,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4009,18 +3531,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4033,18 +3543,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4069,18 +3567,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4093,18 +3579,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4117,18 +3591,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4141,18 +3603,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4181,7 +3631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "wit-parser", ] @@ -4192,7 +3642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "indexmap", "prettyplease", "syn", @@ -4223,7 +3673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags", "indexmap", "log", "serde", @@ -4352,9 +3802,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4363,9 +3813,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4375,18 +3825,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4395,18 +3845,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4419,26 +3869,12 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4447,9 +3883,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4458,90 +3894,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "zip" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" -dependencies = [ - "aes", - "arbitrary", - "bzip2", - "constant_time_eq", - "crc32fast", - "deflate64", - "flate2", - "getrandom 0.3.4", - "hmac", - "indexmap", - "lzma-rust2", - "memchr", - "pbkdf2", - "ppmd-rust", - "sha1", - "time", - "zeroize", - "zopfli", - "zstd", -] - -[[package]] -name = "zlib-rs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" - [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml old mode 100755 new mode 100644 index 34673fb..3127b12 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,37 @@ [package] -name = "Omega" +name = "Omikron" version = "0.1.0" edition = "2024" [dependencies] -epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } -epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } +ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" } +ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" } + +ansi_term = "*" +uuid = { version = "*", features = ["v4"] } -actix-web = { version = "4.12.1", features = ["rustls-0_23"] } -aes-gcm = "*" -ansi_term = "0.12.1" -anyhow = "1.0.101" base64 = "0.22.1" -dashmap = "6.1.0" -dotenv = "0.15.0" -hex = "0.4.3" -hkdf = "0.12.4" -json = "0.12.4" +dashmap = "*" +futures = "*" +hex = "*" once_cell = "1.21.3" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = { version = "0.13.2" } rustls = { version = "0.23.37", default-features = false, features = [ "std", "tls12", "aws-lc-rs", "prefer-post-quantum", ] } -rustls-pemfile = "2.2.0" +aes-gcm = "0.10.3" sha2 = "0.10.9" -sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } -strum = "0.27.2" -strum_macros = "0.27.2" tokio = { version = "*", features = ["full"] } -uuid = { version = "1.19.0", features = ["v4"] } -x448 = "0.6.0" -zip = "6.0.0" +x448 = { version = "*" } +log = "0.4" +dotenv = "0.15.0" +hkdf = "0.12.4" +strum = "0.28.0" +strum_macros = "0.28.0" +livekit-api = { version = "0.4.14", features = ["native-tls"] } +livekit-protocol = "0.7.1" thiserror = "2.0.18" diff --git a/README.md b/README.md index a2c50d5..06c1cdb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# Omega -A loadballancer and Omikron-manager for Tensamin +# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omikron) ⚠️ diff --git a/src/anonymous_clients/anonymous_client_connection.rs b/src/anonymous_clients/anonymous_client_connection.rs new file mode 100644 index 0000000..8c57490 --- /dev/null +++ b/src/anonymous_clients/anonymous_client_connection.rs @@ -0,0 +1,519 @@ +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +use crate::anonymous_clients::anonymous_manager::{self, generate_username}; +use crate::calls::call_manager; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::rho::rho_manager; +use crate::util::logger::PrintType; +use crate::{log_cv_in, log_cv_out, log_out}; + +pub struct AnonymousClientConnection { + user_id: u64, + + pub sender: Arc, + pub receiver: Arc, + pub ping: Arc>, + pub interested_users: Arc>>, + is_open: Arc>, + pub user_name: Arc>, + pub display_name: Arc>, + pub avatar: Arc>, +} + +impl AnonymousClientConnection { + pub async fn from_general(general: Arc, user_id: u64) -> Arc { + let username: String = generate_username(); + Arc::new(Self { + user_id: user_id, + + ping: Arc::new(RwLock::new(0)), + interested_users: Arc::new(RwLock::new(Vec::new())), + is_open: Arc::new(RwLock::new(true)), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + user_name: Arc::new(RwLock::new(username.to_lowercase())), + display_name: Arc::new(RwLock::new(username)), + avatar: Arc::new(RwLock::new(String::new())), + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + while let Ok(cv) = self_clone.receiver.receive().await { + self_clone.clone().handle_message(cv).await; + } + self_clone.handle_close().await; + }); + } + + /// Get the user ID + pub fn get_user_id(&self) -> u64 { + self.user_id + } + + /// Get the user name + pub async fn get_user_name(&self) -> String { + self.user_name.read().await.clone() + } + + /// Get the display name + pub async fn get_display_name(&self) -> String { + self.display_name.read().await.clone() + } + + pub async fn set_display_name(&self, display: String) { + *self.display_name.write().await = display; + } + + /// Get the avatar + pub async fn get_avatar(&self) -> String { + self.avatar.read().await.clone() + } + + /// Send a CommunicationValue to the client + pub async fn send_message(self: Arc, cv: &CommunicationValue) { + if !*self.is_open.read().await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Attempted to send message to a closed connection." + ); + return; + } + if !cv.is_type(CommunicationType::pong) { + log_cv_out!(PrintType::Client, &cv); + } + if let Err(e) = self.sender.send(&cv).await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Send failed: {:?}", + e + ); + } + } + + /// Handle incoming message from client + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + tokio::spawn(async move { + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_cv_in!(PrintType::Client, &cv); + + if cv.is_type(CommunicationType::identification) { + let call_id = + Uuid::parse_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")) + .unwrap_or(Uuid::new_v4()); + + let call = if let Some(call) = call_manager::get_call(call_id).await { + if call.is_anonymous().await { + call + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + return; + } + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + return; + }; + + let mut invited = Vec::new(); + for call_invitee in call.members.read().await.clone() { + let call_invitee_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::get_user_data).add_data( + DataTypes::user_id, + DataValue::Number(call_invitee.user_id as i64), + ), + Some(Duration::from_secs(2)), + ) + .await + .unwrap(); + let mut json_invitee = Vec::new(); + let _ = json_invitee.push(( + DataTypes::user_id, + call_invitee_cv.get_data(DataTypes::user_id).clone(), + )); + let _ = json_invitee.push(( + DataTypes::username, + call_invitee_cv.get_data(DataTypes::username).clone(), + )); + let _ = json_invitee.push(( + DataTypes::display, + call_invitee_cv.get_data(DataTypes::display).clone(), + )); + let _ = json_invitee.push(( + DataTypes::avatar, + call_invitee_cv.get_data(DataTypes::avatar).clone(), + )); + + let _ = invited.push(DataValue::Container(json_invitee)); + } + + let token = call.create_anonymous_token(self.get_user_id()).await; + + let mut serialized = Vec::new(); + let _ = serialized.push((DataTypes::call_id, DataValue::Str(call_id.to_string()))); + let _ = + serialized.push((DataTypes::call_invited, DataValue::Array(invited.clone()))); + let _ = serialized.push((DataTypes::call_members, DataValue::Array(invited))); + let _ = serialized.push((DataTypes::call_token, DataValue::Str(token.unwrap()))); + self.clone() + .send_message( + &&CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, DataValue::Number(self.user_id as i64)) + .add_data( + DataTypes::username, + DataValue::Str(self.clone().get_user_name().await), + ) + .add_data( + DataTypes::display, + DataValue::Str(self.get_display_name().await), + ) + .add_data(DataTypes::avatar, DataValue::Str(self.get_avatar().await)) + .add_data(DataTypes::call_state, DataValue::Container(serialized)), + ) + .await; + } + + // 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::call_token) { + self.handle_get_call(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_disconnect_user) { + self.handle_call_disconnect_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_timeout_user) { + self.handle_call_timeout_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::change_user_data) { + if let Some(display_name) = cv.get_data(DataTypes::display).as_str() { + let _ = self.set_display_name(display_name.to_string()).await; + } + + return; + } + + if cv.is_type(CommunicationType::get_user_data) { + if let Some(anonymous) = { + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + anonymous_manager::get_anonymous_user(user_id as u64).await + } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { + anonymous_manager::get_anonymous_user_by_name(username.to_string()).await + } else { + None + } + } { + let response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data( + DataTypes::username, + DataValue::Str(anonymous.get_user_name().await), + ) + .add_data( + DataTypes::user_id, + DataValue::Number(anonymous.user_id as i64), + ) + .add_data( + DataTypes::display, + DataValue::Str(anonymous.get_display_name().await), + ) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())) + .add_data( + DataTypes::avatar, + DataValue::Str(anonymous.get_avatar().await), + ); + + self.send_message(&response).await; + + return; + } + } + + if cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::delete_user) + { + self.handle_omega_forward(cv).await; + return; + } + }); + } + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let client_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + client_for_closure.send_message(&response_cv).await; + } + }); + } + + /// Handle ping message + async fn handle_ping(self: Arc, cv: CommunicationValue) { + // Update our ping if provided + if let DataValue::Number(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; + } + } + + // Send pong response + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + + self.send_message(&response).await; + } + + /// Handle client status change + async fn handle_client_changed(self: Arc, _cv: CommunicationValue) { + /*let user_id = self.get_user_id().await; + if let Some(_status_str) = cv.get_data(DataTypes::user_state) { + let user_status = UserStatus::online; + }*/ + } + + /// Handle call invite + async fn handle_call_invite(self: Arc, cv: CommunicationValue) { + let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); + if receiver_id == 0 { + self.send_error_response(&cv.get_id(), CommunicationType::error_no_user_id) + .await; + return; + } + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_call_id, + ) + .await; + return; + } + }, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error_no_call_id) + .await; + return; + } + }; + + let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; + if !invited { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + + // Find target RhoConnection + let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { + Some(rho) => rho, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Get sender user ID + let sender_id = self.get_user_id(); + + // Create and send call distribution message + let forward = CommunicationValue::new(CommunicationType::call_invite) + .with_receiver(receiver_id as u64) + .with_sender(sender_id) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data( + DataTypes::receiver_id, + DataValue::Str(receiver_id.to_string()), + ) + .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); + + target_rho.message_to_client(forward).await; + + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle get call request + async fn handle_get_call(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id(); + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(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; + } + }, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + if let Some(token) = call_manager::get_call_token(user_id, call_id).await { + let response = CommunicationValue::new(CommunicationType::call_token) + .with_id(cv.get_id()) + .with_receiver(user_id) + .add_data(DataTypes::call_token, DataValue::Str(token.to_string())); + self.send_message(&response).await; + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + } + async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id()) + .await + .unwrap() + .has_admin() + { + call.get_caller(user_id as u64) + .await + .unwrap() + .set_timeout(untill) + .await; + } + } + } + async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id()) + .await + .unwrap() + .has_admin() + { + call.remove_caller(user_id as u64).await; + } + } + } + + /// Send error response + async fn send_error_response(self: Arc, message_id: &u32, 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 is_open_guard = self.is_open.write().await; + if !*is_open_guard { + return; + } + *is_open_guard = false; + + let _ = self.sender.close(); + } + + #[allow(dead_code)] + /// Set interested users list + pub async fn set_interested_users(self: Arc, interested_ids: Vec) { + let mut interested_guard = self.interested_users.write().await; + *interested_guard = interested_ids; + } + #[allow(dead_code)] + pub async fn get_interested_users(self: Arc) -> Vec { + let interested_guard = self.interested_users.read().await; + interested_guard.clone() + } + + #[allow(dead_code)] + /// Check if interested in a user and send notification + pub async fn are_you_interested(self: Arc, user_id: i64) { + let interested_guard = self.clone().get_interested_users().await; + if interested_guard.contains(&user_id) { + let notification = CommunicationValue::new(CommunicationType::client_changed) + .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(¬ification).await; + } + } + + /// Handle connection close + pub async fn handle_close(&self) { + + // TODO delete temp user + } +} + +// Implement Clone to make it easier to work with Arc +impl Clone for AnonymousClientConnection { + fn clone(&self) -> Self { + Self { + sender: Arc::clone(&self.sender), + receiver: Arc::clone(&self.receiver), + user_id: self.user_id, + ping: Arc::clone(&self.ping), + interested_users: Arc::clone(&self.interested_users), + is_open: Arc::clone(&self.is_open), + user_name: Arc::clone(&self.user_name), + display_name: Arc::clone(&self.display_name), + avatar: Arc::clone(&self.avatar), + } + } +} diff --git a/src/anonymous_clients/anonymous_manager.rs b/src/anonymous_clients/anonymous_manager.rs new file mode 100644 index 0000000..edfefde --- /dev/null +++ b/src/anonymous_clients/anonymous_manager.rs @@ -0,0 +1,53 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::Rng; +use rand::seq::SliceRandom; +use std::sync::Arc; + +use crate::anonymous_clients::anonymous_client_connection::AnonymousClientConnection; + +static ANONYMOUS_USERS: Lazy>> = + Lazy::new(|| DashMap::new()); + +#[allow(dead_code)] +pub async fn add_anonymous_user(connection: Arc) { + ANONYMOUS_USERS.insert(connection.get_user_id(), connection); +} + +#[allow(dead_code)] +pub async fn remove_anonymous_user(user_id: u64) { + ANONYMOUS_USERS.remove(&user_id); +} + +pub async fn get_anonymous_user(user_id: u64) -> Option> { + ANONYMOUS_USERS.get(&user_id).map(|c| c.clone()) +} + +pub async fn get_anonymous_user_by_name( + username: String, +) -> Option> { + for user_conn in ANONYMOUS_USERS + .iter() + .map(|ref_multi| ref_multi.value().clone()) + { + if user_conn.get_user_name().await == username { + return Some(user_conn); + } + } + + return None; +} + +// TODO: implement check if taken +pub fn generate_username() -> String { + let adjectives = ["Swift", "Clever", "Brave", "Sneaky", "Fierce"]; + let nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Dragon"]; + + let mut rng = rand::thread_rng(); + let adj = adjectives.choose(&mut rng).unwrap(); + let noun = nouns.choose(&mut rng).unwrap(); + + let number: u16 = rng.gen_range(0..10000); + + format!("{}{}{}", adj, noun, number) +} diff --git a/src/anonymous_clients/mod.rs b/src/anonymous_clients/mod.rs new file mode 100644 index 0000000..3edd0b4 --- /dev/null +++ b/src/anonymous_clients/mod.rs @@ -0,0 +1,2 @@ +pub mod anonymous_client_connection; +pub mod anonymous_manager; diff --git a/src/calls/call_group.rs b/src/calls/call_group.rs new file mode 100644 index 0000000..91e85cf --- /dev/null +++ b/src/calls/call_group.rs @@ -0,0 +1,106 @@ +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; + +use std::{env, sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::{ + calls::{call_util, caller::Caller}, + omega::omega_connection::get_omega_connection, +}; + +pub struct CallGroup { + pub call_id: Uuid, + pub members: RwLock>>, + pub show: RwLock, + pub anonymous_joining: RwLock, + pub short_link: RwLock>, +} + +impl CallGroup { + pub fn new(call_id: Uuid, user: Arc) -> Self { + CallGroup { + call_id, + members: RwLock::new(vec![user]), + show: RwLock::new(true), + anonymous_joining: RwLock::new(false), + short_link: RwLock::new(None), + } + } + + pub async fn get_caller(&self, user_id: u64) -> Option> { + self.members + .read() + .await + .iter() + .find(|caller| caller.user_id == user_id) + .cloned() + } + + pub async fn is_anonymous(&self) -> bool { + *self.anonymous_joining.read().await + } + + pub async fn set_anonymous_joining(&self, enable: bool) { + *self.anonymous_joining.write().await = enable; + + let _ = call_util::set_room_metadata( + self.call_id, + format!("{{\"anonymous_joining\": \"{}\"}}", enable), + ) + .await; + + if self.short_link.read().await.is_none() { + let long_link = format!( + "https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}", + self.call_id, + env::var("ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0), + ); + let response_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::shorten_link) + .add_data(DataTypes::link, DataValue::Str(long_link)), + Some(Duration::from_secs(20)), + ) + .await; + if let Ok(response) = response_cv { + *self.short_link.write().await = Some( + response + .get_data(DataTypes::link) + .as_str() + .unwrap() + .to_string(), + ); + log::info!( + "Shortened link for call {} is {}", + self.call_id, + self.short_link.read().await.as_ref().unwrap() + ); + } + } + } + + pub async fn create_anonymous_token(&self, user_id: u64) -> Option { + if self.is_anonymous().await { + if let Ok(token) = call_util::create_token(user_id, self.call_id, false) { + return Some(token); + } + } + None + } + + pub async fn remove_caller(&self, user_id: u64) { + let _ = call_util::remove_participant(self.call_id, user_id).await; + self.members + .write() + .await + .retain(|caller| caller.user_id != user_id); + } + + pub async fn get_short_link(self: Arc) -> Option { + self.short_link.read().await.clone() + } +} diff --git a/src/calls/call_manager.rs b/src/calls/call_manager.rs new file mode 100644 index 0000000..a17f347 --- /dev/null +++ b/src/calls/call_manager.rs @@ -0,0 +1,97 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::sync::Arc; +use uuid::Uuid; + +use crate::calls::{call_group::CallGroup, caller::Caller}; + +pub static CALL_GROUPS: Lazy>> = Lazy::new(|| DashMap::new()); +#[allow(dead_code)] +pub async fn get_call_invites(user_id: u64) -> Vec> { + let mut callers = Vec::new(); + for (_, cg) in CALL_GROUPS.clone().into_iter() { + let members = cg.members.read().await; + for member in members.iter() { + if member.user_id == user_id { + callers.push(member.clone()); + } + } + } + callers +} + +pub async fn get_call(call_id: Uuid) -> Option> { + if let Some(b) = CALL_GROUPS.get(&call_id) { + Some(b.clone()) + } else { + None + } +} + +pub async fn get_call_groups(user_id: u64) -> Vec> { + let mut call_groups = Vec::new(); + for (_, cg) in CALL_GROUPS.clone().into_iter() { + let is_member = { + let members = cg.members.read().await; + members.iter().any(|m| m.user_id == user_id) + }; + + if is_member { + call_groups.push(cg.clone()); + } + } + call_groups +} + +pub async fn get_call_token(user_id: u64, call_id: Uuid) -> Option { + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let mut members = cg.members.write().await; + + if let Some(member) = members.iter().find(|m| m.user_id == user_id) { + return Some(member.create_token()); + } + + let new_caller = Arc::new(Caller::new(user_id, call_id, false)); + let token = new_caller.create_token(); + + members.push(new_caller); + + return Some(token); + } + + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let cg_clone = cg.clone(); + + let mut members = cg_clone.members.write().await; + if let Some(member) = members.iter().find(|m| m.user_id == user_id) { + return Some(member.create_token()); + } + let new_caller = Arc::new(Caller::new(user_id, call_id, false)); + let token = new_caller.create_token(); + members.push(new_caller); + return Some(token); + } + + let caller = Arc::new(Caller::new(user_id, call_id, true)); + let call_group = CallGroup::new(call_id, caller.clone()); + + CALL_GROUPS.insert(call_id, Arc::new(call_group)); + + Some(caller.create_token()) +} + +pub async fn add_invite(call_id: Uuid, inviter_id: u64, invitee_id: u64) -> bool { + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let mut members = cg.members.write().await; + + let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id); + + if is_inviter_member { + if !members.iter().any(|m| m.user_id == invitee_id) { + members.push(Arc::new(Caller::new(invitee_id, call_id, false))); + } + return true; + } + } + false +} diff --git a/src/calls/call_util.rs b/src/calls/call_util.rs new file mode 100644 index 0000000..57df005 --- /dev/null +++ b/src/calls/call_util.rs @@ -0,0 +1,151 @@ +use livekit_api::{ + access_token::{self}, + services::room::RoomClient, +}; +use livekit_protocol::Room; +use std::env; +use std::str::FromStr; +use std::time::Duration; +use uuid::Uuid; + +use crate::{calls::call_manager::CALL_GROUPS, log, log_err, util::logger::PrintType}; + +pub fn get_livekit() -> Result<(String, String, String), ()> { + let hostname = match env::var("LIVEKI_HOSTNAME") { + Ok(secret) => secret, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKI_HOSTNAME not set!"); + return Err(()); + } + }; + let api_key = match env::var("LIVEKIT_API_KEY") { + Ok(key) => key, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKIT_API_KEY not set!"); + return Err(()); + } + }; + let api_secret = match env::var("LIVEKIT_API_SECRET") { + Ok(secret) => secret, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKIT_API_SECRET not set!"); + return Err(()); + } + }; + Ok((hostname, api_key, api_secret)) +} + +pub fn create_token(user_id: u64, call_id: Uuid, has_admin: bool) -> Result { + let (_, api_key, api_secret) = get_livekit()?; + + let token = access_token::AccessToken::with_api_key(&api_key, &api_secret) + .with_identity(&user_id.to_string()) + .with_grants(access_token::VideoGrants { + room_join: true, + room_admin: has_admin, + room: call_id.to_string(), + ..Default::default() + }) + .with_metadata(&format!("{{\"isAdmin\":{}}}", has_admin)) + .to_jwt(); + if let Ok(token) = token { + Ok(token) + } else { + Err(()) + } +} +#[allow(dead_code)] +pub async fn get_room(call_id: Uuid) -> Result<(RoomClient, Room), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + let rooms = room_service.list_rooms(Vec::new()).await; + if let Ok(rooms) = rooms { + for room in rooms { + if room.name == call_id.to_string() { + return Ok((room_service, room)); + } + } + } + } + return Err(()); +} + +pub async fn remove_participant(call_id: Uuid, user_id: u64) -> Result<(), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + if let Ok(_) = room_service + .remove_participant(&call_id.to_string(), &user_id.to_string()) + .await + { + return Ok(()); + } + } + return Err(()); +} + +#[allow(dead_code)] +pub async fn get_room_metadata(call_id: Uuid) -> Result { + if let Ok((_, room)) = get_room(call_id).await { + Ok(room.metadata) + } else { + Err(()) + } +} + +pub async fn set_room_metadata(call_id: Uuid, metadata: String) -> Result<(), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + if let Ok(_) = room_service + .update_room_metadata(&call_id.to_string(), &metadata) + .await + { + return Ok(()); + } + } + return Err(()); +} + +pub fn garbage_collect_calls() { + tokio::spawn(async move { + loop { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + clean_calls(room_service).await; + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + }); +} +pub async fn clean_calls(room_service: RoomClient) { + let rooms = room_service.list_rooms(Vec::new()).await.unwrap(); + let mut call_ids: Vec = Vec::new(); + let mut no_users: Vec = Vec::new(); + for room in rooms { + if let Ok(id) = Uuid::from_str(&room.name) { + if room.num_participants == 0 { + no_users.push(id); + } + call_ids.push(id); + } + } + let size_pre = CALL_GROUPS.len(); + for (id, _) in CALL_GROUPS.clone().into_iter() { + if !call_ids.contains(&id) { + CALL_GROUPS.remove(&id); + } + } + for (_, cg) in CALL_GROUPS.clone().into_iter() { + *cg.show.write().await = !no_users.contains(&cg.call_id); + } + + let size_post = CALL_GROUPS.len(); + if size_pre - size_post != 0 { + log!( + 0, + PrintType::Call, + "Cleaned {} calls, {} remaining", + size_pre - size_post, + size_post + ); + } +} diff --git a/src/calls/caller.rs b/src/calls/caller.rs new file mode 100644 index 0000000..74e0c3b --- /dev/null +++ b/src/calls/caller.rs @@ -0,0 +1,49 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::calls::call_util; + +pub struct Caller { + pub user_id: u64, + pub call_id: Uuid, + pub has_admin: bool, + pub timeout: RwLock, +} + +impl Caller { + pub fn new(user_id: u64, call_id: Uuid, has_admin: bool) -> Self { + Caller { + user_id, + call_id, + has_admin, + timeout: RwLock::new(0), + } + } + #[allow(dead_code)] + pub fn set_admin(&mut self, has_admin: bool) { + self.has_admin = has_admin; + } + pub fn has_admin(&self) -> bool { + self.has_admin + } + #[allow(dead_code)] + pub async fn is_timeouted(&self) -> bool { + *self.timeout.read().await + > SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64 + } + pub async fn set_timeout(&self, timeout: i64) { + *self.timeout.write().await = timeout; + } + pub fn create_token(&self) -> String { + if let Ok(token) = call_util::create_token(self.user_id, self.call_id, self.has_admin()) { + token + } else { + String::new() + } + } +} diff --git a/src/calls/mod.rs b/src/calls/mod.rs new file mode 100644 index 0000000..8413354 --- /dev/null +++ b/src/calls/mod.rs @@ -0,0 +1,4 @@ +pub mod call_group; +pub mod call_manager; +pub mod call_util; +pub mod caller; diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src/sql/connection_status.rs b/src/data/user.rs similarity index 100% rename from src/sql/connection_status.rs rename to src/data/user.rs diff --git a/src/main.rs b/src/main.rs index 8143c9f..fa511ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,25 @@ -mod server; -mod sql; -mod transport; +mod anonymous_clients; +mod calls; +mod data; +mod omega; +mod rho; mod util; -use crate::sql::sql::initialize_db; -use crate::sql::sql::print_users; -use crate::transport::omikron_connection; -use crate::util::crypto_helper::load_public_key; -use crate::util::crypto_helper::load_secret_key; -use crate::util::logger::PrintType; -use crate::util::logger::startup; +use std::env; + use dotenv::dotenv; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; -use std::env; + +use crate::{ + calls::call_util::garbage_collect_calls, + omega::omega_connection::get_omega_connection, + rho::server::start, + util::{ + crypto_helper::{load_public_key, load_secret_key}, + logger::startup, + }, +}; static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); pub fn get_private_key() -> x448::Secret { @@ -32,34 +38,14 @@ async fn main() { } dotenv().ok(); startup(); - log_in!("Incoming messages"); - log_out!("Outgoing messages"); + get_omega_connection(); tokio::spawn(async move { - match omikron_connection::start(9187).await { - Err(e) => log_err!(0, PrintType::General, "{:?}", e), - _ => {} + if let Err(e) = start(959).await { + log_err!(0, util::logger::PrintType::General, "{}", e); } }); - - log!("Started"); - log!(" .env"); - if let Err(e) = initialize_db().await { - log!("[FATAL] Database initialization failed: {}", e); - log!( - "[FATAL] Please ensure the database is running and the .env file is configured correctly." - ); - return; - } else { - log!(" DB"); - } - if let Err(e) = print_users().await { - log!("[ERROR] Failed to print users: {}", e); - } else { - log!(" Users"); - } - - let _ = server::server::start(9188).await; + garbage_collect_calls(); tokio::signal::ctrl_c().await.unwrap(); } 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..ab84eb2 --- /dev/null +++ b/src/omega/omega_connection.rs @@ -0,0 +1,741 @@ +use crate::{ + data::user::UserStatus, + get_private_key, log, log_cv_in, log_cv_out, log_err, log_in, + rho::rho_manager::{self, RHO_CONNECTIONS, connection_count}, + util::{ + crypto_helper::{decrypt_b64, secret_key_to_base64}, + file_util::load_file_vec, + logger::PrintType, + }, +}; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::{collections::HashMap, env, sync::Arc, time::Duration}; +use tokio::{ + sync::{Mutex, RwLock, mpsc, watch}, + task::JoinHandle, + time::{Instant, sleep}, +}; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +// ============================================================================ +// Configuration +// ============================================================================ + +const OMEGA_HOST_DEFAULT: &str = "methanium.net"; +const OMEGA_PORT_DEFAULT: u16 = 9187; +const RECONNECT_DELAY: Duration = Duration::from_secs(5); +const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); +const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); +const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); +const TASK_MAX_AGE: Duration = Duration::from_secs(60); + +// ============================================================================ +// Waiting Task System +// ============================================================================ + +pub struct WaitingTask { + pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub inserted_at: Instant, +} + +pub static WAITING_TASKS: Lazy> = Lazy::new(DashMap::new); + +pub fn start_task_cleanup_loop() { + tokio::spawn(async { + loop { + sleep(TASK_CLEANUP_INTERVAL).await; + WAITING_TASKS.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE); + } + }); +} + +// ============================================================================ +// Connection State +// ============================================================================ + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ConnectionState { + Disconnected, + Connecting, + Connected { identified: bool }, +} + +#[allow(unused_variables)] +impl ConnectionState { + pub fn is_connected(&self) -> bool { + match self { + ConnectionState::Connected { identified } => true, + _ => false, + } + } + + #[allow(dead_code)] + pub fn is_identified(&self) -> bool { + match self { + ConnectionState::Connected { identified: true } => true, + _ => false, + } + } +} + +// ============================================================================ +// Omega Connection (Client-side with auto-reconnect) +// ============================================================================ +#[allow(dead_code)] +pub struct OmegaConnection { + state: Arc>, + sender: Arc>>>, + connection_loop_handle: Arc>>>, + host: String, + port: u16, + server_cert: Vec, + last_ping: Arc>, + heartbeat_handle: Arc>>>, + message_send_times: Arc>>, + pub connection_id: Uuid, + shutdown_tx: Arc>>>, + // Track if we should reconnect on close + reconnect_on_close: Arc>, +} + +impl OmegaConnection { + pub fn new() -> Self { + Self::with_host(OMEGA_HOST_DEFAULT, OMEGA_PORT_DEFAULT) + } + + pub fn with_host(host: &str, port: u16) -> Self { + // Load server certificate from default location + let server_cert = + load_file_vec("certs", "cert.pem").expect("Failed to load server certificate"); + + Self::with_host_and_cert(host, port, server_cert) + } + + // New constructor that accepts certificate directly + pub fn with_host_and_cert(host: &str, port: u16, server_cert: Vec) -> Self { + let (shutdown_tx, _) = watch::channel(false); + + OmegaConnection { + state: Arc::new(RwLock::new(ConnectionState::Disconnected)), + sender: Arc::new(RwLock::new(None)), + connection_loop_handle: Arc::new(Mutex::new(None)), + host: host.to_string(), + port, + server_cert, + last_ping: Arc::new(Mutex::new(-1)), + heartbeat_handle: Arc::new(Mutex::new(None)), + message_send_times: Arc::new(Mutex::new(HashMap::new())), + connection_id: Uuid::new_v4(), + shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), + reconnect_on_close: Arc::new(RwLock::new(true)), + } + } + + // ------------------------------------------------------------------------- + // Connection Management + // ------------------------------------------------------------------------- + + pub async fn start(self: Arc) { + // Cancel any existing connection loop + if let Some(handle) = self.connection_loop_handle.lock().await.take() { + handle.abort(); + } + + // Set reconnect flag + *self.reconnect_on_close.write().await = true; + + let self_clone = self.clone(); + let handle = tokio::spawn(async move { + self_clone.connection_loop().await; + }); + + *self.connection_loop_handle.lock().await = Some(handle); + } + + #[allow(dead_code)] + pub async fn stop(&self) { + // Disable reconnection + *self.reconnect_on_close.write().await = false; + + if let Some(tx) = self.shutdown_tx.lock().await.take() { + let _ = tx.send(true); + } + + if let Some(handle) = self.connection_loop_handle.lock().await.take() { + handle.abort(); + } + + if let Some(handle) = self.heartbeat_handle.lock().await.take() { + handle.abort(); + } + + // Close sender if connected + if let Some(sender) = self.sender.read().await.as_ref() { + sender.close(); + } + + *self.state.write().await = ConnectionState::Disconnected; + *self.sender.write().await = None; + } + + async fn connection_loop(self: Arc) { + let mut reconnect_delay = RECONNECT_DELAY; + let shutdown_rx = self.shutdown_tx.lock().await.as_ref().unwrap().subscribe(); + let mut shutdown_rx = shutdown_rx; + + loop { + if *shutdown_rx.borrow() { + log_in!(0, PrintType::Omega, "Connection loop shutting down"); + break; + } + + // Check if reconnection is enabled + if !*self.reconnect_on_close.read().await { + log_in!(0, PrintType::Omega, "Reconnection disabled, exiting loop"); + break; + } + + match self.clone().connect_once().await { + Ok(()) => { + // Connection closed gracefully, check if we should reconnect + if *self.reconnect_on_close.read().await { + log_err!( + 0, + PrintType::Omega, + "Connection lost, reconnecting in {:?}...", + reconnect_delay + ); + } else { + log_in!(0, PrintType::Omega, "Connection closed, not reconnecting"); + break; + } + } + Err(e) => { + log_err!( + 0, + PrintType::Omega, + "Connection failed: {}, retrying in {:?}...", + e, + reconnect_delay + ); + } + } + + tokio::select! { + _ = sleep(reconnect_delay) => {} + _ = shutdown_rx.changed() => { + if *shutdown_rx.borrow() { + break; + } + } + } + + reconnect_delay = std::cmp::min(reconnect_delay * 2, MAX_RECONNECT_DELAY); + } + } + + async fn connect_once(self: Arc) -> Result<(), String> { + *self.state.write().await = ConnectionState::Connecting; + + let addr_str = format!("https://{}:{}", self.host, self.port); + + let (sender, mut receiver) = ttp_native::client::connect(&addr_str, None) + .await + .map_err(|e| format!("Connection failed: {}", e))?; + + log_in!( + 0, + PrintType::Omega, + "QUIC connection established to {}", + addr_str + ); + + // Store sender + let sender_arc = Arc::new(sender); + *self.sender.write().await = Some(sender_arc.clone()); + *self.state.write().await = ConnectionState::Connected { identified: false }; + + // Get handle for close monitoring + let sender_handle = sender_arc.handle().clone(); + + // Start read loop + let read_self = self.clone(); + let read_handle = tokio::spawn(async move { + read_self.read_loop(&mut receiver, sender_handle).await; + }); + + // Send identification + self.send_identification().await; + + // Start heartbeat + let heartbeat_self = self.clone(); + let heartbeat_handle = tokio::spawn(async move { + heartbeat_self.heartbeat_loop().await; + }); + *self.heartbeat_handle.lock().await = Some(heartbeat_handle); + + // Wait for read loop to complete (connection closed) + let result = read_handle.await; + + // Cleanup + *self.sender.write().await = None; + *self.state.write().await = ConnectionState::Disconnected; + + if let Some(handle) = self.heartbeat_handle.lock().await.take() { + handle.abort(); + } + + match result { + Ok(()) => { + // Check if we should reconnect + if *self.reconnect_on_close.read().await { + Err("Connection closed, will reconnect".to_string()) + } else { + Ok(()) + } + } + Err(e) => Err(format!("Read loop error: {}", e)), + } + } + + // ------------------------------------------------------------------------- + // Identification Handshake + // ------------------------------------------------------------------------- + + async fn send_identification(&self) { + let id = rand_u32(); + + let omikron_id = env::var("ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0); + + let identify_msg = CommunicationValue::new(CommunicationType::identification) + .with_id(id) + .add_data(DataTypes::omikron_id, DataValue::Number(omikron_id)); + + WAITING_TASKS.insert( + id, + WaitingTask { + task: Box::new(|selfc, cv| { + if cv.is_type(CommunicationType::error_not_found) { + log_err!( + 0, + PrintType::Omega, + "Identification failed: Omikron ID not found" + ); + return false; + } + if !cv.is_type(CommunicationType::challenge) { + return false; + } + + tokio::spawn(async move { + if let Err(e) = selfc.handle_challenge(cv).await { + log_err!(0, PrintType::Omega, "Challenge handling failed: {}", e); + } + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(&identify_msg).await; + } + + async fn handle_challenge(&self, cv: CommunicationValue) -> Result<(), String> { + let challenge = cv + .get_data(DataTypes::challenge) + .as_str() + .ok_or("Challenge not found")?; + + let server_pub_key = cv + .get_data(DataTypes::public_key) + .as_str() + .ok_or("Public key not found")?; + + let decrypted_challenge = decrypt_b64( + &secret_key_to_base64(&get_private_key()), + server_pub_key, + challenge, + ) + .map_err(|e| format!("Decryption failed: {:?}", e))?; + + let response_msg = CommunicationValue::new(CommunicationType::challenge_response) + .with_id(cv.get_id()) + .add_data(DataTypes::challenge, DataValue::Str(decrypted_challenge)); + + let response_id = response_msg.get_id(); + + WAITING_TASKS.insert( + response_id, + WaitingTask { + task: Box::new(|selfc, final_cv| { + if !final_cv.is_type(CommunicationType::identification_response) { + log_err!(0, PrintType::Omega, "Expected identification_response"); + return false; + } + + let accepted = final_cv + .get_data(DataTypes::accepted) + .as_bool() + .unwrap_or(false); + + if !accepted { + log_err!(0, PrintType::Omega, "Omega did not accept identification"); + return false; + } + + tokio::spawn(async move { + let mut state = selfc.state.write().await; + if let ConnectionState::Connected { identified: _ } = *state { + *state = ConnectionState::Connected { identified: true }; + } + drop(state); + + selfc.sync_client_iota_status().await; + }); + + log!(0, PrintType::Omega, "Successfully identified with Omega"); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(&response_msg).await; + Ok(()) + } + + async fn sync_client_iota_status(self: Arc) { + let mut connected_iota_ids: Vec = Vec::new(); + let mut connected_user_ids: Vec = Vec::new(); + + let rho_connections_reader = RHO_CONNECTIONS.read().await; + + for iota_id in rho_connections_reader.keys() { + connected_iota_ids.push(DataValue::Number(*iota_id)); + } + + for rho in rho_connections_reader.values() { + for client_conn in rho.get_client_connections().await { + connected_user_ids.push(DataValue::Number(client_conn.get_user_id().await as i64)); + } + } + + drop(rho_connections_reader); + + let sync_msg = CommunicationValue::new(CommunicationType::sync_client_iota_status) + .add_data(DataTypes::iota_ids, DataValue::Array(connected_iota_ids)) + .add_data(DataTypes::user_ids, DataValue::Array(connected_user_ids)) + .add_data( + DataTypes::rho_connections, + DataValue::Number(connection_count().await as i64), + ); + + self.send_message(&sync_msg).await; + } + + // ------------------------------------------------------------------------- + // Read Loop & Heartbeat + // ------------------------------------------------------------------------- + + async fn read_loop( + self: Arc, + receiver: &mut Receiver, + sender_handle: Arc, + ) { + // Monitor both receiver and sender handle for close + let mut close_rx = sender_handle.subscribe_close(); + + loop { + tokio::select! { + result = receiver.receive() => { + match result { + Ok(cv) => { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_in!(PrintType::Omega, &cv); + } + + if cv.is_type(CommunicationType::pong) || cv.is_type(CommunicationType::ping) { + self.handle_pong(&cv).await; + continue; + } + + let msg_id = cv.get_id(); + if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { + if (task.task)(self.clone(), cv.clone()) { + continue; + } + } + + if cv.is_type(CommunicationType::iota_user_data) { + if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { + let mut user_ids: Vec = Vec::new(); + for value in users { + if let DataValue::Number(user_id) = value { + user_ids.push(*user_id as u64); + } + } + let connections = crate::rho::rho_manager::RHO_CONNECTIONS.read().await; + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Some(rho) = connections.get(&iota_id) { + rho.get_iota_connection().set_user_ids(user_ids).await; + } + } else { + for rho in connections.values() { + rho.get_iota_connection().set_user_ids(user_ids.clone()).await; + } + } + } + } + } + Err(e) => { + log_err!(0, PrintType::Omega, "Receive error: {}", e); + break; + } + } + } + _ = close_rx.changed() => { + // Connection was closed by either side + if let Some(reason) = close_rx.borrow().clone() { + log_err!(0, PrintType::Omega, "Connection closed: {:?}", reason); + } else { + log_in!(0, PrintType::Omega, "Connection closed cleanly"); + } + break; + } + } + } + } + + async fn heartbeat_loop(self: Arc) { + loop { + sleep(HEARTBEAT_INTERVAL).await; + + // Check if still connected + if !self.state.read().await.is_connected() { + break; + } + + // Check if sender is closed + if let Some(sender) = self.sender.read().await.as_ref() { + if sender.is_closed() { + log_err!(0, PrintType::Omega, "Sender closed, stopping heartbeat"); + break; + } + } else { + break; + } + + self.send_ping().await; + } + } + + async fn send_ping(&self) { + let ping = CommunicationValue::new(CommunicationType::ping).add_data( + DataTypes::send_time, + DataValue::Number( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64, + ), + ); + self.send_message(&ping).await; + } + + async fn handle_pong(&self, cv: &CommunicationValue) { + let timestamp = cv + .get_data(DataTypes::send_time) + .as_number() + .unwrap_or_else(|| { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64 + }); + + *self.last_ping.lock().await = timestamp; + } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + + pub async fn send_message(&self, cv: &CommunicationValue) { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Omega, &cv); + } + + let sender_guard = self.sender.read().await; + if let Some(sender) = sender_guard.as_ref() { + // Check if closed before sending + if sender.is_closed() { + log_err!(0, PrintType::Omega, "Cannot send: connection closed"); + drop(sender_guard); + // Trigger reconnection by closing the connection state + if let Some(sender) = self.sender.write().await.take() { + sender.close(); + } + return; + } + + let sender_clone = Arc::clone(sender); + drop(sender_guard); + + if let Err(e) = sender_clone.send(cv).await { + log_err!(0, PrintType::Omega, "Send failed: {}", e); + } + } else { + log_err!(0, PrintType::Omega, "Cannot send: not connected"); + } + } + + pub async fn await_connection(&self, timeout_duration: Option) -> Result<(), String> { + if self.state.read().await.is_connected() { + return Ok(()); + } + + let timeout = timeout_duration.unwrap_or(CONNECTION_TIMEOUT); + let start = Instant::now(); + + loop { + if self.state.read().await.is_connected() { + return Ok(()); + } + + if start.elapsed() >= timeout { + return Err(format!( + "Connection not established within {} seconds", + timeout.as_secs() + )); + } + + sleep(Duration::from_millis(100)).await; + } + } + + pub async fn await_response( + &self, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + self.await_connection(timeout_duration).await?; + + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + WAITING_TASKS.insert( + msg_id, + WaitingTask { + task: Box::new(move |_, response_cv| { + let inner_tx = tx.clone(); + tokio::spawn(async move { + let _ = inner_tx.send(response_cv).await; + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(_) => Err("Channel closed".to_string()), + Err(_) => { + WAITING_TASKS.remove(&msg_id); + Err("Request timed out".to_string()) + } + } + } + + #[allow(dead_code)] + pub async fn is_connected(&self) -> bool { + self.state.read().await.is_connected() + } + #[allow(dead_code)] + pub async fn is_identified(&self) -> bool { + self.state.read().await.is_identified() + } + + #[allow(dead_code)] + pub async fn close_iota(iota_id: i64) { + let cv = CommunicationValue::new(CommunicationType::iota_disconnected) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + OMEGA_CONNECTION.send_message(&cv).await; + } + + pub async fn client_changed(_iota_id: i64, user_id: i64, state: UserStatus) { + let msg_type = match state { + UserStatus::iota_offline => CommunicationType::user_disconnected, + UserStatus::user_offline => CommunicationType::user_disconnected, + _ => CommunicationType::user_connected, + }; + + let cv = CommunicationValue::new(msg_type) + .add_data(DataTypes::user_id, DataValue::Number(user_id)); + OMEGA_CONNECTION.send_message(&cv).await; + } + + pub async fn user_states(user_id: i64, user_ids: Vec) { + let user_ids = user_ids.iter().map(|v| DataValue::Number(*v)).collect(); + + let cv = CommunicationValue::new(CommunicationType::get_states) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + let msg_id = cv.get_id(); + + WAITING_TASKS.insert( + msg_id, + WaitingTask { + task: Box::new( + move |_: Arc, response: CommunicationValue| { + tokio::spawn(async move { + 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 + }, + ), + inserted_at: Instant::now(), + }, + ); + + OMEGA_CONNECTION.send_message(&cv).await; + } +} + +// ============================================================================ +// Global Instance +// ============================================================================ + +static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { + let conn = Arc::new(OmegaConnection::new()); + + // Start the connection manager immediately + let conn_clone = conn.clone(); + tokio::spawn(async move { + conn_clone.start().await; + }); + + start_task_cleanup_loop(); + + conn +}); + +pub fn get_omega_connection() -> Arc { + OMEGA_CONNECTION.clone() +} diff --git a/src/omega/ping_pong_task.rs b/src/omega/ping_pong_task.rs new file mode 100644 index 0000000..c7fc0b9 --- /dev/null +++ b/src/omega/ping_pong_task.rs @@ -0,0 +1,41 @@ +use std::time::Duration; +use tokio::time::Instant; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; + +use crate::omega::omega_connection::OmegaConnection; + +const PING_TIMEOUT: Duration = Duration::from_secs(30); + +impl OmegaConnection { + pub async fn send_ping(&self) { + let id = rand_u32(); + let send_time = Instant::now(); + + let mut message_send_times = self.message_send_times.lock().await; + message_send_times.retain(|_uuid, time| time.elapsed() < PING_TIMEOUT); + message_send_times.insert(id as i64, send_time); + + self.send_ping_message(id).await; + } + + pub async fn send_ping_message(&self, id: u32) { + let ping_message = CommunicationValue::new(CommunicationType::ping) + .with_id(id) + .add_data( + DataTypes::last_ping, + DataValue::Number(self.last_ping.lock().await.unwrap()), + ); + + self.send_message(&ping_message).await; + } + + /// Handles incoming pong and calculates latency + pub async fn handle_pong(&self, cv: &CommunicationValue, _log: bool) { + let id = cv.get_id(); + let mut message_send_times = self.message_send_times.lock().await; + if let Some(send_time) = message_send_times.remove(&(id as i64)) { + let ping = Instant::now().duration_since(send_time).as_millis() as i64; + *self.last_ping.lock().await = ping; + } + } +} diff --git a/src/rho/client_connection.rs b/src/rho/client_connection.rs new file mode 100644 index 0000000..ebaabf2 --- /dev/null +++ b/src/rho/client_connection.rs @@ -0,0 +1,583 @@ +use crate::anonymous_clients::anonymous_manager; +use crate::calls::{call_manager, call_util}; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::rho::{rho_connection::RhoConnection, rho_manager}; +use crate::util::logger::PrintType; +use crate::{data::user::UserStatus, omega::omega_connection::OmegaConnection}; +use crate::{log_cv_in, log_cv_out, log_err, log_in, log_out}; +use std::str::FromStr; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +pub struct ClientConnection { + pub user_id: u64, + + pub sender: Arc, + pub receiver: Arc, + pub ping: Arc>, + pub_key: Arc>>>, + pub rho_connection: Arc>>>, + pub interested_users: Arc>>, + is_open: Arc>, +} + +impl ClientConnection { + pub async fn from_general(general: Arc, user_id: u64) -> Arc { + Arc::new(Self { + ping: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: general.rho_connection.clone(), + interested_users: Arc::new(RwLock::new(Vec::new())), + is_open: Arc::new(RwLock::new(true)), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + user_id: user_id, + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + while let Ok(cv) = self_clone.receiver.receive().await { + self_clone.clone().handle_message(cv).await; + } + self_clone.handle_close().await; + }); + + let self_clone2 = self.clone(); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(50)).await; + if self_clone2.get_rho_connection().await.is_none() { + self_clone2 + .send_error_response(0, CommunicationType::error) + .await; + } + }); + } + + /// Get the user ID + pub async fn get_user_id(&self) -> u64 { + self.user_id + } + + /// Get current ping + pub async fn get_ping(&self) -> i64 { + *self.ping.read().await + } + + /// Get RhoConnection if available + pub async fn get_rho_connection(&self) -> Option> { + self.rho_connection.read().await.clone() + } + + /// Send a CommunicationValue to the client + pub async fn send_message(self: Arc, cv: &CommunicationValue) { + if !*self.is_open.read().await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Attempted to send message to a closed connection." + ); + return; + } + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Client, &cv); + } + let _ = self.sender.send(&cv).await; + } + + /// Handle incoming message from client + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + tokio::spawn(async move { + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_cv_in!(PrintType::Client, cv); + + // 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::call_token) { + self.handle_get_call(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_disconnect_user) { + self.handle_call_disconnect_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_timeout_user) { + self.handle_call_timeout_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_set_anonymous_joining) { + self.handle_call_set_anonymous_joining(cv).await; + return; + } + if cv.is_type(CommunicationType::get_user_data) { + if let Some(anonymous) = { + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + anonymous_manager::get_anonymous_user(user_id as u64).await + } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { + anonymous_manager::get_anonymous_user_by_name(username.to_string()).await + } else { + None + } + } { + let response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data( + DataTypes::username, + DataValue::Str(anonymous.get_user_name().await), + ) + .add_data( + DataTypes::user_id, + DataValue::Number(anonymous.get_user_id() as i64), + ) + .add_data( + DataTypes::display, + DataValue::Str(anonymous.get_display_name().await), + ) + .add_data( + DataTypes::avatar, + DataValue::Str(anonymous.get_avatar().await), + ) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(&response).await; + + return; + } + } + + if cv.is_type(CommunicationType::change_user_data) + || cv.is_type(CommunicationType::read_notification) + || cv.is_type(CommunicationType::get_notifications) + || cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::delete_user) + { + let sender = self.get_user_id().await; + self.handle_omega_forward(cv.with_sender(sender as u64)) + .await; + return; + } + // Forward other messages to Iota + self.forward_to_iota(cv).await; + }); + } + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let client_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + client_for_closure.send_message(&response_cv).await; + } + }); + } + + /// Handle ping message + async fn handle_ping(self: Arc, cv: CommunicationValue) { + // Update our ping if provided + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + let current = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + let mut ping_guard = self.ping.write().await; + *ping_guard = current as i64 - last_ping; + } + + // 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(DataTypes::ping_iota, DataValue::Number(iota_ping)); + + self.send_message(&response).await; + } + + /// Handle client status change + async fn handle_client_changed(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id().await; + if let DataValue::Str(_status_str) = cv.get_data(DataTypes::user_state) { + let user_status = UserStatus::user_online; + if let Some(rho_conn) = self.get_rho_connection().await { + OmegaConnection::client_changed( + rho_conn.get_iota_id().await as i64, + user_id as i64, + user_status, + ) + .await; + } + } + } + + /// Handle call invite + async fn handle_call_invite(self: Arc, cv: CommunicationValue) { + let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); + if receiver_id == 0 { + self.send_error_response(cv.get_id(), CommunicationType::error_no_user_id) + .await; + return; + } + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + }, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error_no_call_id) + .await; + return; + } + }; + + let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; + if !invited { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + + // Find target RhoConnection + let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { + Some(rho) => rho, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Get sender user ID + let sender_id = self.get_user_id().await; + + // Create and send call distribution message + let forward = CommunicationValue::new(CommunicationType::call_invite) + .with_receiver(receiver_id as u64) + .with_sender(sender_id as u64) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data( + DataTypes::receiver_id, + DataValue::Str(receiver_id.to_string()), + ) + .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); + + target_rho.message_to_client(forward).await; + + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle get call request + async fn handle_get_call(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id().await; + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + if let Some(token) = call_manager::get_call_token(user_id, call_id).await { + let response = CommunicationValue::new(CommunicationType::call_token) + .with_id(cv.get_id()) + .with_receiver(user_id as u64) + .add_data(DataTypes::call_token, DataValue::Str(token)); + self.send_message(&response).await; + } else { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + } + async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + let _ = call_util::remove_participant(call_id, user_id as u64).await; + call.get_caller(user_id as u64) + .await + .unwrap() + .set_timeout(untill) + .await; + } + } + } + async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + call.remove_caller(user_id as u64).await; + } + } + } + async fn handle_call_set_anonymous_joining(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let enable = cv.get_data(DataTypes::enabled).as_bool().unwrap_or(true); + + let call = call_manager::get_call(call_id).await; + + let mut short_link = None; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + call.set_anonymous_joining(enable).await; + } + short_link = call.get_short_link().await; + } + let mut response_cv = + CommunicationValue::new(CommunicationType::call_set_anonymous_joining) + .with_id(cv.get_id()) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data(DataTypes::enabled, DataValue::Bool(enable)); + if let Some(short_link) = short_link { + response_cv = response_cv.add_data(DataTypes::link, DataValue::Str(short_link)); + } + self.send_message(&response_cv).await; + } + + /// Forward message to Iota + async fn forward_to_iota(self: Arc, cv: CommunicationValue) { + let sender_user_id = self.get_user_id().await; + let msg_id = cv.get_id(); + let msg_type = cv.get_type(); + + log_in!( + sender_user_id as i64, + PrintType::Client, + "Forwarding client->iota: sender={} type={:?} id={} receiver={}", + sender_user_id, + msg_type, + msg_id, + cv.get_receiver() + ); + + if cv.is_type(CommunicationType::add_conversation) + && cv + .get_data(DataTypes::chat_partner_id) + .as_number() + .is_none() + { + let chat_partner_name = cv + .get_data(DataTypes::chat_partner_name) + .as_str() + .unwrap_or("") + .to_string(); + + if anonymous_manager::get_anonymous_user_by_name(chat_partner_name.to_string()) + .await + .is_some() + { + self.send_error_response(cv.get_id(), CommunicationType::error_anonymous) + .await; + return; + } + + let load_uuid_response = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.clone().get_id()) + .add_data( + DataTypes::username, + DataValue::Str(chat_partner_name.clone()), + ), + Some(Duration::from_secs(20)), + ) + .await; + let chat_partner_id = { + if let Ok(load_uuid_response) = load_uuid_response { + load_uuid_response.get_data(DataTypes::user_id).clone() + } else { + DataValue::Null + } + }; + + if let Some(rho_conn) = self.get_rho_connection().await { + let iota_id = rho_conn.get_iota_id().await; + log_in!( + sender_user_id as i64, + PrintType::Client, + "Resolved rho for add_conversation: sender={} -> iota_id={} id={}", + sender_user_id, + iota_id, + msg_id + ); + + let updated_cv = cv + .with_sender(sender_user_id as u64) + .add_data(DataTypes::chat_partner_id, chat_partner_id); + rho_conn.message_to_iota(updated_cv).await; + } else { + log_err!( + sender_user_id as i64, + PrintType::Client, + "No rho/iota mapping found for add_conversation sender={} type={:?} id={}", + sender_user_id, + msg_type, + msg_id + ); + } + return; + } + + if let Some(rho_conn) = self.get_rho_connection().await { + let iota_id = rho_conn.get_iota_id().await; + log_in!( + sender_user_id as i64, + PrintType::Client, + "Resolved rho for forward: sender={} -> iota_id={} type={:?} id={}", + sender_user_id, + iota_id, + msg_type, + msg_id + ); + + let updated_cv = cv.with_sender(sender_user_id as u64); + rho_conn.message_to_iota(updated_cv).await; + } else { + log_err!( + sender_user_id as i64, + PrintType::Client, + "No rho/iota mapping found for sender={} type={:?} id={}", + sender_user_id, + msg_type, + msg_id + ); + self.send_error_response(msg_id, CommunicationType::error) + .await; + } + } + + /// Send error response + async fn send_error_response(self: Arc, message_id: u32, 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 is_open_guard = self.is_open.write().await; + if !*is_open_guard { + return; + } + *is_open_guard = false; + + let _ = self.sender.close(); + } + + /// Set interested users list + pub async fn set_interested_users(self: Arc, interested_ids: Vec) { + let mut interested_guard = self.interested_users.write().await; + *interested_guard = interested_ids; + } + #[allow(dead_code)] + pub async fn get_interested_users(self: Arc) -> Vec { + let interested_guard = self.interested_users.read().await; + interested_guard.clone() + } + + /// Check if interested in a user and send notification + #[allow(dead_code)] + pub async fn are_you_interested(self: Arc, user_id: i64) { + let interested_guard = self.clone().get_interested_users().await; + if interested_guard.contains(&user_id) { + let notification = CommunicationValue::new(CommunicationType::client_changed) + .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(¬ification).await; + } + } + + /// Handle connection close + pub async fn handle_close(&self) { + let user_id = self.get_user_id().await; + if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id as i64).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 { + sender: Arc::clone(&self.sender), + receiver: Arc::clone(&self.receiver), + user_id: self.user_id, + ping: Arc::clone(&self.ping), + pub_key: Arc::clone(&self.pub_key), + rho_connection: Arc::clone(&self.rho_connection), + interested_users: Arc::clone(&self.interested_users), + is_open: Arc::clone(&self.is_open), + } + } +} diff --git a/src/rho/connection.rs b/src/rho/connection.rs new file mode 100755 index 0000000..ca746f0 --- /dev/null +++ b/src/rho/connection.rs @@ -0,0 +1,358 @@ +use rand::{Rng, distributions::Alphanumeric}; +use std::{sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; + +use crate::{ + anonymous_clients::anonymous_client_connection::AnonymousClientConnection, + get_private_key, get_public_key, log_cv_in, log_cv_out, log_err, log_in, log_out, + omega::omega_connection::get_omega_connection, + rho::{ + client_connection::ClientConnection, iota_connection::IotaConnection, + rho_connection::RhoConnection, rho_manager, + }, + util::{ + crypto_helper::{load_public_key, public_key_to_base64}, + crypto_util::{DataFormat, SecurePayload}, + logger::PrintType, + }, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(dead_code)] +pub enum ConnectionKind { + Client, + Iota, + AnonymousClient, + Phi, +} + +pub struct GeneralConnection { + pub sender: Arc, + pub receiver: Arc, + + identified: Arc>, + challenged: Arc>, + challenge: Arc>, + + connection_kind: Arc>>, + pub rho_connection: Arc>>>, + id: Arc>, + + pub_key: Arc>>>, +} +impl GeneralConnection { + pub fn new(sender: Sender, receiver: Receiver) -> Arc { + Arc::new(Self { + sender: Arc::new(sender), + receiver: Arc::new(receiver), + identified: Arc::new(RwLock::new(false)), + challenged: Arc::new(RwLock::new(false)), + challenge: Arc::new(RwLock::new(String::new())), + connection_kind: Arc::new(RwLock::new(None)), + rho_connection: Arc::new(RwLock::new(None)), + id: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + }) + } +} +impl GeneralConnection { + pub async fn handle(self: Arc) { + log_in!(0, PrintType::General, "General connection handler started"); + + loop { + let cv = match self.receiver.receive().await { + Ok(v) => v, + Err(_) => { + break; + } + }; + + log_cv_in!(cv); + + if !*self.identified.read().await { + self.handle_identification(cv).await; + continue; + } + + if !*self.challenged.read().await { + self.handle_challenge_response(cv).await; + } + + if *self.challenged.read().await { + let self_clone = self.clone(); + tokio::spawn(async move { + self_clone.migrate().await; + }); + break; + } + } + + log_out!(0, PrintType::General, "General connection handler stopped"); + } + async fn handle_identification(self: &Arc, cv: CommunicationValue) { + if !cv.is_type(CommunicationType::identification) { + return; + } + + if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { + *self.id.write().await = *iota_id as u64; + *self.connection_kind.write().await = Some(ConnectionKind::Iota); + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .add_data(DataTypes::iota_id, DataValue::Number(*iota_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + let response_cv = match response_cv { + Ok(r) => r, + Err(_) => { + return; + } + }; + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .as_str() + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + *self.identified.write().await = true; + + let encrypted_challenge = + SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(public_key_to_base64(&get_public_key())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); + + log_cv_out!(response); + let _ = self.sender.send(&response).await; + } else if let DataValue::Number(user_id) = cv.get_data(DataTypes::user_id) { + *self.id.write().await = *user_id as u64; + *self.connection_kind.write().await = Some(ConnectionKind::Client); + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) + .add_data(DataTypes::user_id, DataValue::Number(*user_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + let response_cv = match response_cv { + Ok(r) => r, + Err(_) => { + return; + } + }; + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .as_str() + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + *self.identified.write().await = true; + + let encrypted_challenge = + SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(public_key_to_base64(&get_public_key())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); + + log_cv_out!(response); + let _ = self.sender.send(&response).await; + } + } + async fn handle_challenge_response(self: &Arc, cv: CommunicationValue) { + let id = *self.id.read().await as i64; + + if !cv.is_type(CommunicationType::challenge_response) { + return; + } + + if let DataValue::Str(response) = cv.get_data(DataTypes::challenge) { + let expected = self.challenge.read().await.clone(); + + if *response == expected { + *self.challenged.write().await = true; + + let response = CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, DataValue::Bool(true)); + + log_cv_out!(response); + if let Err(_) = self.sender.send(&response).await { + return; + } + } else { + log_err!( + id, + PrintType::Iota, + "Challenge response mismatch expected={} actual={}", + expected, + response + ); + } + } else { + log_err!( + id, + PrintType::Iota, + "Challenge response missing challenge payload" + ); + } + } + + async fn migrate(self: &Arc) -> bool { + let kind = match *self.connection_kind.read().await { + Some(kind) => kind, + None => { + return false; + } + }; + let id = *self.id.read().await; + + match kind { + ConnectionKind::Client => { + let notify = CommunicationValue::new(CommunicationType::user_connected) + .add_data(DataTypes::user_id, DataValue::Number(id as i64)); + get_omega_connection().send_message(¬ify).await; + + let user_id = id as i64; + + let client = ClientConnection::from_general(self.clone(), id).await; + + let mut rho = rho_manager::get_rho_con_for_user(user_id).await; + + if rho.is_none() { + let get_user_msg = CommunicationValue::new(CommunicationType::get_user_data) + .add_data(DataTypes::user_id, DataValue::Number(user_id)); + + if let Ok(user_data_cv) = get_omega_connection() + .await_response(&get_user_msg, Some(Duration::from_secs(20))) + .await + { + if let DataValue::Number(iota_id) = + user_data_cv.get_data(DataTypes::iota_id) + { + if let Some(bound_rho) = + rho_manager::bind_user_to_iota(user_id, *iota_id).await + { + bound_rho.bind_user_id(user_id).await; + rho = Some(bound_rho); + } + } + } + } + + *self.rho_connection.write().await = rho.clone(); + + if let Some(rho_conn) = rho { + rho_conn.bind_user_id(user_id).await; + rho_conn.add_client_connection(client.clone()).await; + } else { + log_err!( + user_id, + PrintType::Client, + "No RhoConnection found for user {}, client not attached to iota", + id + ); + } + + client.start(); + } + ConnectionKind::Iota => { + let notify = CommunicationValue::new(CommunicationType::iota_connected) + .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); + get_omega_connection().send_message(¬ify).await; + + let iota = IotaConnection::from_general(self.clone(), id).await; + + let rho = Arc::new(RhoConnection::new(iota.clone(), Vec::new()).await); + + iota.set_rho_connection(rho.clone()).await; + + rho_manager::add_rho(rho).await; + + let get_iota_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); + + if let Ok(iota_data_cv) = get_omega_connection() + .await_response(&get_iota_msg, Some(Duration::from_secs(20))) + .await + { + if let DataValue::Array(users) = iota_data_cv.get_data(DataTypes::user_ids) { + let mut user_ids: Vec = Vec::new(); + for value in users { + if let DataValue::Number(user_id) = value { + user_ids.push(*user_id as u64); + } + } + iota.set_user_ids(user_ids).await; + } + } + + iota.clone().start(); + } + ConnectionKind::AnonymousClient => { + let client = AnonymousClientConnection::from_general(self.clone(), id).await; + client.start(); + } + ConnectionKind::Phi => { + let phi = ClientConnection::from_general(self.clone(), id).await; + phi.start(); + } + } + true + } +} diff --git a/src/rho/iota_connection.rs b/src/rho/iota_connection.rs new file mode 100755 index 0000000..41336ea --- /dev/null +++ b/src/rho/iota_connection.rs @@ -0,0 +1,461 @@ +use crate::calls::call_group::CallGroup; +use crate::calls::call_manager; +use crate::log_cv_in; +use crate::log_cv_out; +use crate::log_err; +use crate::log_in; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::util::logger::PrintType; +use dashmap::DashMap; +use std::collections::BTreeMap; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use tokio::sync::mpsc; +use ttp_core::CommunicationType; +use ttp_core::CommunicationValue; +use ttp_core::DataTypes; +use ttp_core::DataValue; +use ttp_native::Receiver; +use ttp_native::Sender; +use x448::PublicKey; + +use super::{rho_connection::RhoConnection, rho_manager}; +use crate::omega::omega_connection::OmegaConnection; + +#[allow(dead_code)] +pub struct IotaConnection { + pub iota_id: u64, + pub sender: Arc, + pub receiver: Arc, + pub user_ids: Arc>>, + pub ping: Arc>, + pub_key: Arc>>>, + pub waiting_tasks: + DashMap, CommunicationValue) -> bool + Send + Sync>>, + pub rho_connection: Arc>>>, +} + +impl IotaConnection { + pub async fn from_general(general: Arc, iota_id: u64) -> Arc { + Arc::new(Self { + ping: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: general.rho_connection.clone(), + user_ids: Arc::new(RwLock::new(Vec::new())), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + iota_id: iota_id, + waiting_tasks: DashMap::new(), + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + loop { + match self_clone.receiver.receive().await { + Ok(cv) => { + self_clone.clone().handle_message(cv).await; + } + Err(_) => { + break; + } + } + } + self_clone.handle_close().await; + }); + } + + /// Get the Iota ID + pub async fn get_iota_id(&self) -> u64 { + self.iota_id + } + + #[allow(dead_code)] + pub async fn get_public_key(&self) -> Option { + if let Some(public_key) = self.pub_key.read().await.clone() { + PublicKey::from_bytes(&public_key) + } else { + None + } + } + + /// Get the user IDs + pub async fn get_user_ids(&self) -> Vec { + self.user_ids.read().await.clone() + } + + /// Replace all users linked to this iota and synchronize the attached rho mapping. + pub async fn set_user_ids(&self, user_ids: Vec) { + { + let mut guard = self.user_ids.write().await; + *guard = user_ids.clone(); + } + + if let Some(rho_conn) = self.get_rho_connection().await { + let user_ids_i64: Vec = user_ids.into_iter().map(|u| u as i64).collect(); + rho_conn.set_user_ids(user_ids_i64).await; + } + } + + pub async fn add_user_id(&self, user_id: u64) { + let mut should_sync = false; + { + let mut guard = self.user_ids.write().await; + if !guard.contains(&user_id) { + guard.push(user_id); + should_sync = true; + } + } + + if should_sync { + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.add_user_id(user_id as i64).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: Arc) { + 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() { + Some(weak_ref.clone()) + } else { + None + } + } + + /// Send a CommunicationValue to the Iota + pub async fn send_message(&self, cv: &CommunicationValue) { + if !cv.is_type(CommunicationType::pong) { + log_cv_out!(PrintType::Iota, cv); + } + if let Err(e) = self.sender.send(&cv).await { + log_err!( + self.iota_id as i64, + PrintType::Iota, + "Failed to send message: {:?}", + e + ); + } + } + + /// Handle incoming message from Iota + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + let msg_id = cv.get_id(); + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + if (task)(self.clone(), cv.clone()) { + return; + } + } + + // Handle ping + if cv.is_type(CommunicationType::ping) || cv.is_type(CommunicationType::pong) { + self.handle_ping(cv).await; + return; + } + + log_cv_in!(PrintType::Iota, cv); + + // Handle GET_CHATS + if cv.is_type(CommunicationType::get_chats) { + self.handle_get_chats(cv).await; + return; + } + + // Handle forwarding to other Iotas or clients + let receiver_id = cv.get_receiver(); + if (receiver_id != 0 && !self.get_user_ids().await.contains(&(receiver_id as u64))) + || cv.is_type(CommunicationType::message_other_iota) + || cv.is_type(CommunicationType::send_chat) + { + self.handle_forward_message(cv).await; + return; + } + + if cv.is_type(CommunicationType::change_iota_data) + || cv.is_type(CommunicationType::push_notification) + || cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::get_register) + || cv.is_type(CommunicationType::complete_register_user) + || cv.is_type(CommunicationType::delete_iota) + { + let sender = self.get_iota_id().await; + self.handle_omega_forward(cv.with_sender(sender as u64)) + .await; + return; + } + self.forward_to_client(cv).await; + } + + #[allow(dead_code)] + async fn send_error_response(&self, message_id: u32, error_type: CommunicationType) { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send_message(&error).await; + } + + #[allow(dead_code)] + async fn close(&self) { + let _ = self.sender.close(); + } + + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let iota_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.iota_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + iota_for_closure.send_message(&response_cv).await; + } + }); + } + /// Handle ping message + async fn handle_ping(&self, cv: CommunicationValue) { + if let DataValue::Number(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; + } + } + + let client_pings = if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.get_client_pings().await + } else { + HashMap::new() + }; + + let pings: Vec<(DataTypes, DataValue)> = client_pings + .into_iter() + .map(|(k, v)| (DataTypes::parse(k), DataValue::Number(v))) + .collect(); + let response = CommunicationValue::new(CommunicationType::pong) + .with_id(cv.get_id()) + .add_data(DataTypes::ping_clients, DataValue::Container(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(); + let sender_id = cv.get_sender(); + let my_user_ids = self.get_user_ids().await; + + log_in!( + self.iota_id as i64, + PrintType::Iota, + "Authority check: sender_id={} receiver_id={} iota_user_ids={:?} msg_type={:?} msg_id={}", + sender_id, + receiver_id, + my_user_ids, + cv.get_type(), + cv.get_id() + ); + + if my_user_ids.contains(&(sender_id as u64)) { + if let Some(target_rho) = rho_manager::get_rho_con_for_user(receiver_id as i64).await { + target_rho.message_to_iota(cv).await; + } else { + let error = CommunicationValue::new(CommunicationType::error_no_iota) + .with_id(cv.get_id()) + .with_sender(cv.get_sender()); + self.send_message(&error).await; + } + } else { + log_err!( + self.iota_id as i64, + PrintType::Iota, + "Rejected client->iota forward: sender_id={} is not authorized for this iota. Known users={:?}", + sender_id, + my_user_ids + ); + + self.send_message( + &CommunicationValue::new(CommunicationType::error_invalid_user_id).add_data( + DataTypes::error_type, + DataValue::Str( + "You are sending to another User without authority.".to_string(), + ), + ), + ) + .await; + } + } + + /// Handle GET_CHATS message + async fn handle_get_chats(&self, cv: CommunicationValue) { + let receiver_id = cv.get_receiver(); + let mut interested_ids: Vec = Vec::new(); + + // ============================ + // Load Calls + // ============================ + let calls: Vec> = call_manager::get_call_groups(receiver_id).await; + + let mut invites: HashMap> = HashMap::new(); + let empty = calls.is_empty(); + + for call in calls { + for inviter in call.members.read().await.iter() { + let call_self = call.get_caller(receiver_id).await.unwrap(); + + let inviter_id = inviter.user_id; + let timeout = *call_self.timeout.read().await; + let admin = call_self.has_admin(); + + // Build call container + let mut call_map: BTreeMap = BTreeMap::new(); + + call_map.insert(DataTypes::call_id, DataValue::Str(call.call_id.to_string())); + + if timeout > 0 { + call_map.insert(DataTypes::timeout, DataValue::Number(timeout as i64)); + } + + if admin { + call_map.insert(DataTypes::has_admin, DataValue::Bool(true)); + } + + let call_container = DataValue::container_from_map(&call_map); + + invites + .entry(inviter_id as i64) + .or_insert_with(Vec::new) + .push(call_container); + } + } + + // ============================ + // Enrich Contacts + // ============================ + let enriched_contacts = if empty { + match cv.get_data(DataTypes::user_ids) { + DataValue::Array(arr) => DataValue::Array(arr.clone()), + _ => DataValue::Array(vec![]), + } + } else { + let mut enriched: Vec = Vec::new(); + + if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { + for user_val in users { + if let DataValue::Container(entries) = user_val { + let mut user_map: BTreeMap = + entries.iter().cloned().collect(); + + // extract user_id + if let Some(DataValue::Number(user_id)) = user_map.get(&DataTypes::user_id) + { + interested_ids.push(*user_id); + + // attach calls if exists + if let Some(call_list) = invites.get(user_id) { + user_map + .insert(DataTypes::calls, DataValue::Array(call_list.clone())); + } + } + + enriched.push(DataValue::container_from_map(&user_map)); + } + } + } + + DataValue::Array(enriched) + }; + + // ============================ + // Notify Omega + // ============================ + OmegaConnection::user_states(receiver_id as i64, interested_ids.clone()).await; + + // ============================ + // Notify Rho + // ============================ + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn + .set_interested(receiver_id as i64, interested_ids) + .await; + } + + // ============================ + // Forward to client + // ============================ + self.forward_to_client(cv.add_data(DataTypes::user_ids, enriched_contacts)) + .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); + rho_conn.message_to_client(updated_cv).await; + } else { + } + } + + pub async fn handle_close(&self) { + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.close_iota_connection().await; + } + } + + #[allow(dead_code)] + pub async fn await_response( + self: Arc, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + let task_tx = tx.clone(); + self.waiting_tasks.insert( + msg_id, + Box::new(move |_, response_cv| { + let inner_tx = task_tx.clone(); + tokio::spawn(async move { + let _ = inner_tx.send(response_cv).await; + }); + true + }), + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(_) => Err("Failed to receive response, channel was closed.".to_string()), + Err(_) => { + self.waiting_tasks.remove(&msg_id); + Err(format!( + "Request timed out after {} seconds.", + timeout.as_secs() + )) + } + } + } +} + +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..ac498f3 --- /dev/null +++ b/src/rho/mod.rs @@ -0,0 +1,6 @@ +pub mod client_connection; +pub mod connection; +pub mod iota_connection; +pub mod rho_connection; +pub mod rho_manager; +pub mod server; diff --git a/src/rho/rho_connection.rs b/src/rho/rho_connection.rs new file mode 100644 index 0000000..6a740ff --- /dev/null +++ b/src/rho/rho_connection.rs @@ -0,0 +1,199 @@ +use super::{client_connection::ClientConnection, iota_connection::IotaConnection, rho_manager}; + +use crate::data::user::UserStatus; +use crate::omega::omega_connection::OmegaConnection; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; + +pub struct RhoConnection { + iota_connection: Arc, + user_ids: Arc>>, + 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: Arc::new(RwLock::new(user_ids.clone())), + client_connections: Arc::new(RwLock::new(Vec::new())), + }; + + rho_connection + } + + pub async fn get_iota_id(&self) -> u64 { + self.iota_connection.iota_id + } + + pub async fn get_user_ids(&self) -> Vec { + self.user_ids.read().await.clone() + } + + pub async fn set_user_ids(&self, user_ids: Vec) { + let mut guard = self.user_ids.write().await; + *guard = user_ids; + } + + pub async fn add_user_id(&self, user_id: i64) { + let mut guard = self.user_ids.write().await; + if !guard.contains(&user_id) { + guard.push(user_id); + } + } + + pub async fn bind_user_id(&self, user_id: i64) { + self.add_user_id(user_id).await; + self.iota_connection.add_user_id(user_id as u64).await; + } + + pub fn get_iota_connection(&self) -> &Arc { + &self.iota_connection + } + + 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: i64, + ) -> Vec> { + let connections = self.client_connections.read().await; + let mut collections = Vec::new(); + for con in connections.iter() { + if con.get_user_id().await == user_id as u64 { + collections.push(con.clone()); + } + } + collections + } + + /// Add a client connection + #[allow(dead_code)] + pub async fn add_client_connection(&self, connection: Arc) { + let notification = CommunicationValue::new(CommunicationType::client_connected).add_data( + DataTypes::user_id, + DataValue::Number(connection.get_user_id().await as i64), + ); + + self.iota_connection.send_message(¬ification).await; + + { + let mut connections = self.client_connections.write().await; + connections.push(Arc::clone(&connection)); + } + + OmegaConnection::client_changed( + self.get_iota_id().await as i64, + connection.get_user_id().await as i64, + UserStatus::user_online, + ) + .await; + } + + /// Remove a client connection + pub async fn close_client_connection(&self, connection: Arc) { + let target_user_id = connection.get_user_id().await; + { + let mut connections = self.client_connections.write().await; + connections.retain(|con| { + futures::executor::block_on(async { con.get_user_id().await != target_user_id }) + }); + } + + // Notify OmegaConnection + OmegaConnection::client_changed( + self.get_iota_id().await as i64, + connection.get_user_id().await as i64, + UserStatus::user_offline, + ) + .await; + } + + /// 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 as i64).await; + + // Notify OmegaConnection + OmegaConnection::close_iota(self.get_iota_id().await as i64).await; + } + + /// Send message from Iota to specific client + pub async fn message_to_client(&self, cv: CommunicationValue) { + let connections = self.client_connections.read().await; + let receiver_id = cv.get_receiver(); + for connection in connections.iter() { + if connection.get_user_id().await == receiver_id { + connection.clone().send_message(&cv).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: i64, interested_ids: Vec) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + let conn_user_id = connection.get_user_id().await; + if conn_user_id == user_id as u64 { + connection + .clone() + .set_interested_users(interested_ids.clone()) + .await; + break; + } + } + } + + /// Check if clients are interested in a user + #[allow(dead_code)] + pub async fn are_they_interested(&self, user_id: i64) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + connection.clone().are_you_interested(user_id).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() { + let 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 + #[allow(dead_code)] + pub async fn contains_user(&self, user_id: &i64) -> bool { + self.user_ids.read().await.contains(user_id) + } + + /// Get count of active client connections + #[allow(dead_code)] + pub async fn client_count(&self) -> usize { + let connections = self.client_connections.read().await; + connections.len() + } +} diff --git a/src/rho/rho_manager.rs b/src/rho/rho_manager.rs new file mode 100644 index 0000000..e823a04 --- /dev/null +++ b/src/rho/rho_manager.rs @@ -0,0 +1,83 @@ +use super::rho_connection::RhoConnection; +use crate::log_in; +use crate::util::logger::PrintType; +use std::{ + collections::HashMap, + sync::{Arc, LazyLock}, +}; +use tokio::sync::RwLock; + +pub static RHO_CONNECTIONS: LazyLock>>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn get_rho_con_for_user(user_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + for rho_connection in connections.values() { + let rho_user_ids = rho_connection.get_user_ids().await; + log_in!( + user_id, + PrintType::Client, + "Comparing user IDs: {:?}", + rho_user_ids + ); + if rho_user_ids.contains(&user_id) { + return Some(Arc::clone(rho_connection)); + } + } + None +} + +#[allow(dead_code)] +pub async fn contains_iota(iota_id: i64) -> bool { + let connections = RHO_CONNECTIONS.read().await; + connections.contains_key(&iota_id) +} + +/// Bind a user ID to an already tracked iota/rho connection. +pub async fn bind_user_to_iota(user_id: i64, iota_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + if let Some(rho_connection) = connections.get(&iota_id) { + let rho = Arc::clone(rho_connection); + drop(connections); + + rho.add_user_id(user_id).await; + + log_in!( + user_id, + PrintType::Client, + "Bound user {} to iota {}", + user_id, + iota_id + ); + + Some(rho) + } else { + None + } +} + +/// Remove a RhoConnection by Iota ID +pub async fn remove_rho(iota_id: i64) -> 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 as i64, rho_connection); +} + +/// Get a RhoConnection by Iota ID directly +#[allow(dead_code)] +pub async fn get_rho_by_iota(iota_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + connections.get(&iota_id).map(Arc::clone) +} + +/// Get the count of active connections +pub async fn connection_count() -> usize { + let connections = RHO_CONNECTIONS.read().await; + connections.len() +} diff --git a/src/rho/server.rs b/src/rho/server.rs new file mode 100644 index 0000000..9e3946d --- /dev/null +++ b/src/rho/server.rs @@ -0,0 +1,25 @@ +use crate::{ + log, + rho::connection::GeneralConnection, + util::{file_util::load_file_vec, logger::PrintType}, +}; +use ttp_native::Host; + +pub async fn start(port: u16) -> Result<(), Box> { + let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); + + let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); + + let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; + log!(0, PrintType::General, "Server listening on port {}", port); + + while let Some((sender, receiver)) = host.next().await { + tokio::spawn(async move { + let conn = GeneralConnection::new(sender, receiver); + conn.handle().await; + }); + } + log!(0, PrintType::General, "Server stopped"); + + Ok(()) +} diff --git a/src/server/api.rs b/src/server/api.rs deleted file mode 100644 index eb6f9cf..0000000 --- a/src/server/api.rs +++ /dev/null @@ -1,275 +0,0 @@ -use crate::get_public_key; -use crate::sql::sql; -use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; -use crate::transport::omikron_manager::get_random_omikron; -use crate::util::file_util::get_directory; -use crate::{ - sql::sql::{get_by_user_id, get_omikron_by_id}, - util::crypto_helper::public_key_to_base64, -}; -use actix_web::HttpResponse; -use actix_web::http::{StatusCode, header}; -use base64::Engine as _; -use json::JsonValue; - -pub async fn handle(path: &str, body_string: Option) -> HttpResponse { - if path == "OPTIONS" { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) - .insert_header(("Access-Control-Allow-Headers", "*")) - .finish(); - } - - let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); - - let _body: Option = if body_string.is_some() { - if let Ok(body_json) = json::parse(&body_string.unwrap()) { - Some(body_json) - } else { - None - } - } else { - None - }; - - let (status, body_text) = match path_parts.as_slice() { - // ================================================== - // DOWNLOAD IOTA FRONTEND - // ================================================== - ["api", "download", "iota_frontend"] => { - let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); - - match std::fs::read(file_path) { - Ok(file_bytes) => { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Content-Type", "application/zip")) - .insert_header(( - "Content-Disposition", - "attachment; filename=\"iota_frontend.zip\"", - )) - .body(file_bytes); - } - Err(_) => { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - return HttpResponse::NotFound() - .insert_header(("Access-Control-Allow-Origin", "*")) - .body(res.dump()); - } - } - } - - // ================================================== - // GET RANDOM OMIKRON - // ================================================== - ["api", "get", "omikron"] => { - if let Ok(omikron_conn) = get_random_omikron().await { - if let Some(id) = omikron_conn.get_omikron_id().await { - if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET OMIKRON BY ID - // ================================================== - ["api", "get", "omikron", id] => { - let id = id.parse::().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - (StatusCode::OK, res.dump()) - } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await - { - if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET ID BY USERNAME - // ================================================== - ["api", "get", "id", username] => { - if username.is_empty() { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - _, - _, - _, - _, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // GET SERVER PUBLIC KEY - // ================================================== - ["api", "get", "public_key"] => { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["public_key"] = public_key_to_base64(&get_public_key()).into(); - (StatusCode::OK, res.dump()) - } - - // ================================================== - // GET USER BY ID - // ================================================== - ["api", "get", "user", id] => { - let id: i64 = id.parse().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - display, - status_msg, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - if let Some(display) = display { - res["display"] = display.into(); - } - if let Some(status_msg) = status_msg { - res["status_message"] = status_msg.into(); - } - if let Some(about) = about { - res["about"] = about.into(); - } - if let Some(avatar) = avatar { - res["avatar"] = base64::engine::general_purpose::STANDARD - .encode(avatar) - .into(); - } - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // DEFAULT - // ================================================== - _ => { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - }; - let body_bytes = body_text.into_bytes(); - - HttpResponse::build(status) - .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) - .body(body_bytes) -} diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 8645e6e..0000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod api; -pub mod server; -pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs deleted file mode 100644 index 3618eb7..0000000 --- a/src/server/server.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::{ - log, - server::{api, short_link::get_short_link}, - util::file_util::load_file_buf, -}; - -use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; - -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use rustls_pemfile::{certs, pkcs8_private_keys}; - -pub async fn start(port: u16) -> anyhow::Result<()> { - let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; - - let mut key_reader = load_file_buf("certs", "server_key.pem")?; - - let cert_chain: Vec> = - certs(&mut cert_reader).collect::>()?; - - let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) - .map(|res| res.map(Into::into)) - .collect::>()?; - - let key = keys.remove(0); - - let mut config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key)?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - let addr = format!("0.0.0.0:{port}"); - log!(" Server on {}", addr); - - HttpServer::new(move || { - App::new() - .route("/api/{path:.*}", web::to(api_handler)) - .route("/direct/{path:.*}", web::to(direct_handler)) - }) - .bind_rustls_0_23(addr, config)? - .run() - .await?; - - Ok(()) -} -async fn direct_handler(req: HttpRequest) -> impl Responder { - let path = req.uri().path().to_string(); - let short = path.replace("/direct/", ""); - - if let Ok(long) = get_short_link(&short).await { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, long)) - .finish() - } else { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, "https://tensamin.net")) - .finish() - } -} - -async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { - let path = req.uri().path().to_string(); - let body_string = String::from_utf8_lossy(&body).to_string(); - - api::handle(&path, Some(body_string)).await -} diff --git a/src/server/short_link.rs b/src/server/short_link.rs deleted file mode 100644 index c0ab51d..0000000 --- a/src/server/short_link.rs +++ /dev/null @@ -1,87 +0,0 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::{Rng, thread_rng}; - -static LINKS: Lazy> = Lazy::new(DashMap::new); - -const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; - -pub async fn add_short_link(long: &str) -> Result { - let raw = generate_unique_short_link().await; - LINKS.insert(raw.clone(), long.to_string()); - - Ok(format!( - "https://omega.tensamin.net/direct/{}", - format_with_dashes(&raw) - )) -} - -async fn generate_unique_short_link() -> String { - loop { - let short = generate_short_link().await; - if !LINKS.contains_key(&short) { - return short; - } - } -} - -pub async fn generate_short_link() -> String { - let len = short_length(); - - let mut rng = thread_rng(); - (0..len) - .map(|_| { - let idx = rng.gen_range(0..CHARSET.len()); - CHARSET[idx] as char - }) - .collect() -} - -pub async fn get_short_link(short: &str) -> Result { - let key = if short.contains("/") { - short.split("/").nth(1).unwrap_or_default() - } else { - short - }; - let frag = short.replace(key, ""); - let normalized = normalize_short(&key); - - if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { - Ok(format!("{}{}", t, frag)) - } else { - Err(()) - } -} - -/* ---------------- helpers ---------------- */ - -fn short_length() -> usize { - let count = LINKS.len(); - - match count { - 0..=1_999 => 4, - 2_000..=999_999 => 8, - _ => 12, - } -} - -fn format_with_dashes(s: &str) -> String { - s.chars() - .collect::>() - .chunks(4) - .map(|c| c.iter().collect::()) - .collect::>() - .join("-") -} - -fn normalize_short(input: &str) -> String { - input - .chars() - .filter(|c| *c != '-') - .map(|c| match c { - 'Q' | 'O' => '0', - 'I' => 'l', - _ => c, - }) - .collect() -} diff --git a/src/sql/mod.rs b/src/sql/mod.rs deleted file mode 100644 index 91ba841..0000000 --- a/src/sql/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod connection_status; -pub mod sql; -pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs deleted file mode 100644 index 8445e26..0000000 --- a/src/sql/sql.rs +++ /dev/null @@ -1,765 +0,0 @@ -use crate::log; -use once_cell::sync::Lazy; -use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::{ - env, - sync::Arc, - time::{SystemTime, UNIX_EPOCH}, -}; -use tokio::sync::RwLock; - -/* -use crate::sql::{ - iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, - sql::{ - change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, - change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, - get_register_id, register_complete_iota, register_complete_user, - }, -}; - */ - -static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); - -pub async fn connect() -> Result, sqlx::Error> { - let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); - let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); - let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); - - MySqlPoolOptions::new() - .max_connections(200) - .connect(&format!( - "mysql://{}:{}@127.0.0.1:3306/{}", - user, passwd, table - )) - .await -} -// Omega -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -pub async fn initialize_db() -> Result<(), sqlx::Error> { - let pool = connect().await?; - let mut db_lock = SQL_DB.write().await; - // create tables - // with indexes - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - users ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, - display VARCHAR(15) COLLATE utf8mb4_bin, - status VARCHAR(15) COLLATE utf8mb4_bin, - about VARCHAR(200) COLLATE utf8mb4_bin, - avatar MEDIUMBLOB, - sub_level INT(11) NOT NULL DEFAULT 0, - sub_end BIGINT(20) NOT NULL DEFAULT 0, - public_key TEXT NOT NULL COLLATE utf8mb4_bin, - private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', - iota_id BIGINT UNSIGNED NOT NULL, - token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - iotas ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - omikrons ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - notifications ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, - sender_id BIGINT UNSIGNED NOT NULL, - receiver_id BIGINT UNSIGNED NOT NULL, - amount BIGINT UNSIGNED NOT NULL DEFAULT 0 - )", - ) - .execute(&pool) - .await; - *db_lock = Some(pool); - Ok(()) -} - -// ========================================================================================== -// REGISTER -// ========================================================================================== -pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); -pub static CURRENT_REGISTER_PROCESS: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); - -pub async fn get_register_id() -> u64 { - let mut current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as u64; - - loop { - let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); - - if current_locked < current_time { - let result = CURRENT_MILLI_USED.compare_exchange( - current_locked, // expected value - current_time, // new value - Ordering::SeqCst, // acquire/release ordering - Ordering::SeqCst, // failure ordering - ); - - match result { - Ok(_) => { - CURRENT_REGISTER_PROCESS.write().await.push(current_time); - return current_time; - } - Err(_) => { - continue; - } - } - } else { - current_time = current_locked + 1; - } - } -} - -// ========================================================================================== -// USERS -// ========================================================================================== - -pub async fn get_by_username( - username: &str, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", - ) - .bind(username) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - username, - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_by_user_id( - id: i64, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - username, - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_users_by_iota_id( - iota_id_param: i64, -) -> Result< - Vec<( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - )>, - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", - ) - .bind(iota_id_param) - .fetch_all(&pool) - .await?; - - let mut users = Vec::new(); - for row in rows { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - users.push(( - id, - iota_id, - username, - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token).to_string(), - )); - } - - Ok(users) -} - -pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_username) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_display) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_avatar) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_about) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_status) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") - .bind(new_iota_id) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_keys( - id: i64, - new_public_key: String, - new_private_key_hash: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query( - "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(new_public_key) - .bind(new_private_key_hash) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_token) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn register_complete_user( - id: i64, - username: String, - public_key: String, - iota_id: i64, - token: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query( - "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", - ) - .bind(id) - .bind(username) - .bind(public_key) - .bind(iota_id) - .bind(token) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn print_users() -> Result<(), Box> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - log!("Printing users..."); - for row in sqlx::query( - "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", - ) - .fetch_all(&pool) - .await? - .iter() - { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - - log!( - "User: {:?}", - ( - id, - iota_id, - username, - display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), - status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), - about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), - sub_level, - sub_end - ) - ); - } - - Ok(()) -} - -// ========================================================================================== -// IOTA -// ========================================================================================== -pub async fn create_new_iota(public_key: String) -> Result { - let new_id = get_register_id().await as i64; - register_complete_iota(new_id, public_key).await?; - Ok(new_id) -} - -pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") - .bind(id) - .bind(public_key) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let result = sqlx::query_as::<_, (u64, Vec)>( - "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await; - - match result { - Ok(optional_row) => match optional_row { - Some((id_u64, public_key)) => Ok(( - id_u64 as i64, - String::from_utf8_lossy(&public_key).to_string(), - )), - _ => Err(sqlx::Error::RowNotFound), - }, - Err(e) => Err(e), - } -} - -pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_key) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -// ========================================================================================== -// OMIKRONS -// ========================================================================================== - -pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some((public_key, ip_address)) => Ok(( - String::from_utf8_lossy(&public_key).to_string(), - String::from_utf8_lossy(&ip_address).to_string(), - )), - _ => Err(sqlx::Error::RowNotFound), - } -} - -// ========================================================================================== -// PHI -// ========================================================================================== - -pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - INSERT INTO notifications (sender_id, receiver_id, amount) - VALUES (?, ?, 1) - ON DUPLICATE KEY UPDATE amount = amount + 1 - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query_as::<_, (i64, i64)>( - r#" - SELECT sender_id, amount FROM notifications WHERE receiver_id = ? - "#, - ) - .bind(user_id) - .fetch_all(pool) - .await -} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs deleted file mode 100644 index 42dfa22..0000000 --- a/src/sql/user_online_tracker.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::sql; -use crate::sql::connection_status::UserStatus; -use dashmap::DashMap; -use once_cell::sync::Lazy; - -#[derive(Debug, Clone)] -pub struct UserConnection { - pub connection_type: UserStatus, - pub omikron_id: i64, -} - -// IotaID -> Primary OmikronID -static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); - -// IotaID -> Vec -static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); - -// UserID -> UserStatus -static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); - -pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { - let mut entry = IOTA_OMIKRON_CONNECTIONS - .entry(iota_id) - .or_insert_with(Vec::new); - - if !entry.contains(&omikron_id) { - entry.push(omikron_id); - } - - if primary { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); - } -} - -pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { - let mut vec = r.value().clone(); - vec.retain(|&id| id != omikron_id); - let empty = vec.is_empty(); - drop(r); - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); - empty - } else { - false - }; - - if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { - let primary_id = *primary_ref.value(); - drop(primary_ref); - if primary_id == omikron_id { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } - } - - if connections_empty { - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); - } - - connections_empty -} - -pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { - IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) -} - -pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { - IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) -} - -pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { - USER_STATUS_MAP.insert( - user_id, - UserConnection { - connection_type: status, - omikron_id, - }, - ); -} - -pub fn get_user_status(user_id: i64) -> Option { - USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) -} - -pub fn untrack_many_users(user_ids: &[i64]) { - for user_id in user_ids { - USER_STATUS_MAP.remove(user_id); - } -} - -pub async fn untrack_omikron(omikron_id: i64) { - let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION - .iter() - .filter(|entry| *entry.value() == omikron_id) - .map(|entry| *entry.key()) - .collect(); - - for key in primary_keys_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); - } - - let mut offline_iotas = Vec::new(); - let mut primary_to_remove = Vec::new(); - - // Collect iotas and primary info first - for r in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *r.key(); - let mut connections = r.value().clone(); - connections.retain(|&id| id != omikron_id); - - if connections.is_empty() { - offline_iotas.push(iota_id); - } - - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) - { - primary_to_remove.push(iota_id); - } - - // Update the connections vector after filtering - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); - } - - // Step 2: Remove primary connections safely - for iota_id in primary_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } - - // Step 3: Remove users that were on this omikron - USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - - // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP - for iota_id in offline_iotas { - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { - for user in users { - USER_STATUS_MAP.remove(&user.0); - } - } - // Finally remove the empty connections vector - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); - } -} diff --git a/src/transport/mod.rs b/src/transport/mod.rs deleted file mode 100644 index 4f3953a..0000000 --- a/src/transport/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod omikron_connection; -pub mod omikron_manager; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs deleted file mode 100644 index 504d723..0000000 --- a/src/transport/omikron_connection.rs +++ /dev/null @@ -1,1139 +0,0 @@ -use crate::{ - get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, - server::short_link::add_short_link, - sql::{ - connection_status::UserStatus, - sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, - user_online_tracker::{self}, - }, - transport::omikron_manager, - util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use dashmap::DashMap; -use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use epsilon_native::{Host, Receiver, Sender}; -use rand::{Rng, distributions::Alphanumeric}; -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; -use tokio::{ - sync::{Mutex, RwLock}, - time::interval, -}; -use x448::PublicKey; - -// ============================================================================ -// Configuration -// ============================================================================ - -const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); -const MAX_WAITING_AGE: Duration = Duration::from_secs(60); - -// ============================================================================ -// Error Types -// ============================================================================ - -#[derive(Debug, thiserror::Error)] -pub enum OmikronError { - #[error("Not connected")] - NotConnected, - #[error("Not authenticated")] - NotAuthenticated, - #[error("Invalid response")] - InvalidResponse, - #[error("Authentication failed")] - AuthenticationFailed, - #[error("SQL error: {0}")] - Sql(String), - #[error("Send error: {0}")] - Send(String), -} - -pub type OmikronResult = Result; - -// ============================================================================ -// Waiting Task System (Preserved from original) -// ============================================================================ - -pub struct WaitingTask { - pub task: Box, CommunicationValue) -> bool + Send + Sync>, - pub inserted_at: Instant, -} - -// ============================================================================ -// Connection State -// ============================================================================ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum AuthState { - Unauthenticated, - Identified { omikron_id: i64 }, - Authenticated { omikron_id: i64 }, -} - -impl AuthState { - fn is_authenticated(&self) -> bool { - match self { - AuthState::Authenticated { omikron_id } => true, - _ => false, - } - } - - fn omikron_id(&self) -> Option { - match self { - AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { - Some(*omikron_id) - } - _ => None, - } - } -} - -// ============================================================================ -// Omikron Connection (Epsilon/QUIC-based) -// ============================================================================ - -pub struct OmikronConnection { - id: u64, - sender: Mutex>, - state: RwLock, - challenge: RwLock, - pub_key: RwLock>>, - pub ping: RwLock, - waiting_tasks: DashMap, - cleanup_handle: Mutex>>, -} - -impl OmikronConnection { - // ------------------------------------------------------------------------- - // Construction - // ------------------------------------------------------------------------- - - pub fn new(sender: Sender) -> Arc { - let conn = Arc::new(Self { - id: rand::random(), - sender: Mutex::new(Some(sender)), - state: RwLock::new(AuthState::Unauthenticated), - challenge: RwLock::new(String::new()), - pub_key: RwLock::new(None), - ping: RwLock::new(-1), - waiting_tasks: DashMap::new(), - cleanup_handle: Mutex::new(None), - }); - - conn - } - - // ------------------------------------------------------------------------- - // Main Handler Loop - // ------------------------------------------------------------------------- - - pub async fn handle(self: Arc, receiver: &mut Receiver) { - log_in!( - self.id as i64, - PrintType::Omega, - "Omikron connection started" - ); - - // Start cleanup task - let cleanup_conn = self.clone(); - let cleanup_handle = tokio::spawn(async move { - let mut ticker = interval(CLEANUP_INTERVAL); - loop { - ticker.tick().await; - cleanup_conn - .waiting_tasks - .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); - } - }); - *self.cleanup_handle.lock().await = Some(cleanup_handle); - - while let Ok(cv) = receiver.receive().await { - if let Err(e) = self.clone().process_message(cv).await { - log_err!(0, PrintType::Omega, "Error processing message: {}", e); - if matches!(e, OmikronError::NotConnected) { - break; - } - } - } - - self.clone().cleanup().await; - log_in!( - self.id as i64, - PrintType::Omega, - "Omikron connection closed" - ); - } - - // ------------------------------------------------------------------------- - // Message Processing - // ------------------------------------------------------------------------- - - async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_in!(PrintType::Omikron, &cv); - } - - let msg_id = cv.get_id(); - - // Check waiting tasks first (response to previous request) - if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { - let _ = (task.task)(self.clone(), cv); - return Ok(()); - } - - // Handle ping regardless of auth state - if cv.is_type(CommunicationType::ping) { - return self.handle_ping(cv).await; - } - - let current_state = *self.state.read().await; - // Route based on authentication state - match current_state { - AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, - AuthState::Identified { .. } => self.clone().handle_identified(cv).await, - AuthState::Authenticated { omikron_id } => { - self.clone().handle_authenticated(cv, omikron_id).await - } - } - } - - // ------------------------------------------------------------------------- - // Authentication Handlers - // ------------------------------------------------------------------------- - - async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::identification) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - // Extract omikron ID - let omikron_id = cv - .get_data(DataTypes::omikron_id) - .as_number() - .ok_or(OmikronError::InvalidResponse)?; - log!("Omikron {:?} connected", omikron_id); - - // Lookup omikron in database - let (public_key, _) = get_omikron_by_id(omikron_id) - .await - .map_err(|e| OmikronError::Sql(e.to_string()))?; - - log!("Got public Key"); - - let pub_key_bytes = STANDARD - .decode(&public_key) - .map_err(|_| OmikronError::AuthenticationFailed)?; - - let pub_key_bytes_clone = pub_key_bytes.clone(); - let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) - .ok_or(OmikronError::AuthenticationFailed)?; - - log!("Decoded public Key"); - - // Generate challenge - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - log!("Generated Challenge"); - - *self.challenge.write().await = challenge.clone(); - - log!("Stored Challenge"); - - *self.pub_key.write().await = Some(pub_key_bytes); - - log!("Stored Pubkey"); - - *self.state.write().await = AuthState::Identified { omikron_id }; - - log!("Stored State"); - - let challenge_clone = challenge.clone(); - let private_key = get_private_key(); - let public_key_for_encrypt = omikron_pub_key; - - let encrypted = tokio::task::spawn_blocking(move || { - encrypt(private_key, public_key_for_encrypt, &challenge_clone) - .map_err(|_| OmikronError::AuthenticationFailed) - }) - .await - .map_err(|_| OmikronError::AuthenticationFailed)??; - - log!("Encrypted Challenge"); - - // Send challenge response - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted)); - - log!("Sending Challenge"); - self.send(&response).await - } - - async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::challenge_response) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - let client_response = cv - .get_data(DataTypes::challenge) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let expected_challenge = self.challenge.read().await.clone(); - - if client_response == expected_challenge { - let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); - *self.state.write().await = AuthState::Authenticated { omikron_id }; - - omikron_manager::add_omikron(self.clone()).await; - - let response = CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::Bool(true)); - - self.clone().send(&response).await?; - log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); - Ok(()) - } else { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) - .await; - Err(OmikronError::AuthenticationFailed) - } - } - - // ------------------------------------------------------------------------- - // Authenticated Message Handlers - // ------------------------------------------------------------------------- - - async fn handle_authenticated( - self: Arc, - cv: CommunicationValue, - omikron_id: i64, - ) -> OmikronResult<()> { - match cv.get_type() { - // Link shortening - CommunicationType::shorten_link => self.handle_shorten_link(cv).await, - - // Online status tracking - CommunicationType::user_connected => { - self.handle_user_connected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::user_disconnected => { - self.handle_user_disconnected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::iota_connected => { - self.handle_iota_connected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::iota_disconnected => { - self.handle_iota_disconnected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::sync_client_iota_status => { - self.handle_sync_status(cv, omikron_id).await; - Ok(()) - } - - CommunicationType::get_user_data => self.handle_get_user_data(cv).await, - CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, - - CommunicationType::get_register => self.handle_get_register(cv).await, - CommunicationType::complete_register_iota => { - self.handle_complete_register_iota(cv).await - } - CommunicationType::complete_register_user => { - self.handle_complete_register_user(cv).await - } - - CommunicationType::change_user_data => self.handle_change_user_data(cv).await, - CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, - CommunicationType::delete_user => self.handle_delete_user(cv).await, - CommunicationType::delete_iota => self.handle_delete_iota(cv).await, - - CommunicationType::get_notifications => self.handle_get_notifications(cv).await, - CommunicationType::read_notification => self.handle_read_notification(cv).await, - CommunicationType::push_notification => self.handle_push_notification(cv).await, - - _ => { - log_err!( - 0, - PrintType::Omega, - "Unknown message type: {:?}", - cv.get_type() - ); - Ok(()) - } - } - } - - // ------------------------------------------------------------------------- - // Specific Handlers (ported from original WebSocket implementation) - // ------------------------------------------------------------------------- - - async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let link = cv - .get_data(DataTypes::link) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let short = add_short_link(link) - .await - .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; - - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, DataValue::Str(short)); - - self.send(&response).await - } - - async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_online, - omikron_id, - ); - } - } - - async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { - log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { - user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_offline, - status.omikron_id, - ); - } - } - } - - async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA connected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - - let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id)); - user_online_tracker::track_user_status( - user_id, - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error loading users for IOTA"); - } - - let response = CommunicationValue::new(CommunicationType::iota_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); - - let _ = self.send(&response).await; - } else { - log_in!(PrintType::General, "No IOTA ID found"); - } - } - - async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA disconnected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } - } - } - } - - async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { - if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { - for user_id_val in user_ids { - if let DataValue::Number(user_id) = user_id_val { - user_online_tracker::track_user_status( - *user_id, - UserStatus::user_online, - omikron_id, - ); - } - } - } - - if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { - for iota_id_val in iota_ids { - if let DataValue::Number(iota_id) = iota_id_val { - user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); - } - } - } - } - - async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by user_id first - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - if let Ok(user_data) = get_by_user_id(user_id as i64).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { - if let Ok(user_data) = get_by_username(username).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - // Not found - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_user_data_response( - self: Arc, - msg_id: u32, - user: ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - ), - ) -> CommunicationValue { - let ( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - ) = user; - - let mut response = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(msg_id) - .add_data(DataTypes::username, DataValue::Str(username.clone())) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::user_id, DataValue::Number(id)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) - .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) - .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); - - // Display name (fallback to username) - let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); - response = response.add_data(DataTypes::display, DataValue::Str(display_name)); - - // Optional fields - if let Some(s) = status.filter(|s| !s.is_empty()) { - response = response.add_data(DataTypes::status, DataValue::Str(s)); - } - if let Some(a) = about.filter(|a| !a.is_empty()) { - response = response.add_data(DataTypes::about, DataValue::Str(a)); - } - if let Some(av) = avatar { - response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); - } - - // Online status - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - if let Some(us) = user_status { - response = response.add_data( - DataTypes::online_status, - DataValue::Str(us.connection_type.to_string()), - ); - response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); - } else { - response = response.add_data( - DataTypes::online_status, - DataValue::Str(UserStatus::iota_offline.to_string()), - ); - } - - response = response.add_data( - DataTypes::omikron_connections, - DataValue::Array( - iota_connections - .into_iter() - .map(DataValue::Number) - .collect(), - ), - ); - - response - } - - async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by iota_id - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; - } - } - - // Try by user_id - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(user_id as i64).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id as i64), - None, - ) - .await; - return self.send(&response).await; - } - } - } - - // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { - if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_username(username).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id), - Some(username.to_string()), - ) - .await; - return self.send(&response).await; - } - } - } - - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_iota_data_response( - self: Arc, - msg_id: u32, - iota_id: i64, - public_key: String, - user_id: Option, - username: Option, - ) -> CommunicationValue { - let mut response = CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(msg_id) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); - - if let Some(uid) = user_id { - response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); - } - if let Some(uname) = username { - response = response.add_data(DataTypes::username, DataValue::Str(uname)); - } - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - response.add_data( - DataTypes::omikron_connections, - DataValue::Array( - iota_connections - .into_iter() - .map(DataValue::Number) - .collect(), - ), - ) - } - - async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::get_register) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); - self.send(&response).await - } - - async fn handle_complete_register_iota( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let iota_id_opt = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); - - if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { - if let Some(iota_id) = iota_id_opt { - // Register existing IOTA - match sql::register_complete_iota(iota_id, public_key.to_string()).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - // Create new IOTA - match sql::create_new_iota(public_key.to_string()).await { - Ok(new_iota_id) => { - let response = - CommunicationValue::new(CommunicationType::complete_register_iota) - .with_id(cv.get_id()) - .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_complete_register_user( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv - .get_data(DataTypes::user_id) - .as_number() - .map(|n| n as i64); - let username = cv - .get_data(DataTypes::username) - .as_str() - .map(|s| s.to_string()); - let public_key = cv - .get_data(DataTypes::public_key) - .as_str() - .map(|s| s.to_string()); - let iota_id = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); - let reset_token = cv - .get_data(DataTypes::reset_token) - .as_str() - .map(|s| s.to_string()); - - if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = - (user_id, username, public_key, iota_id, reset_token) - { - match sql::register_complete_user(uid, uname, pk, iid, rt).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - let mut success = true; - let mut error_message = String::new(); - - // Process each field - if let Some(username) = cv.get_data(DataTypes::username).as_str() { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(display) = cv.get_data(DataTypes::display).as_str() { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(about) = cv.get_data(DataTypes::about).as_str() { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(status) = cv.get_data(DataTypes::status).as_str() { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataTypes::public_key).as_str(), - cv.get_data(DataTypes::private_key_hash).as_str(), - ) { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } else { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); - self.send(&response).await - } - } - - async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - - if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64), - cv.get_data(DataTypes::reset_token).as_str(), - cv.get_data(DataTypes::new_token).as_str(), - ) { - match sql::get_by_user_id(user_id).await { - Ok(user) => { - let current_token = user.11; // reset_token field - if current_token == reset_token { - let mut success = true; - let mut error_message = String::new(); - - if let Err(e) = sql::change_iota_id(user_id, iota_id).await { - success = false; - error_message = e.to_string(); - } - if success { - if let Err(e) = sql::change_token(user_id, new_token.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send(&response).await - } else { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); - self.send(&response).await - } - } else { - self.send_error_response( - cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await - } - } - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error_not_found) - .await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - match sql::delete_user(user_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - - async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(iota_id) = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64) - { - match sql::delete_iota(iota_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_get_notifications( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Ok(notifications) = sql::get_notifications(user_id).await { - let json_array: Vec = notifications - .into_iter() - .map(|(sender, amount)| { - DataValue::Container(vec![ - (DataTypes::sender_id, DataValue::Number(sender)), - (DataTypes::amount, DataValue::Number(amount)), - ]) - }) - .collect(); - - let response = CommunicationValue::new(CommunicationType::get_notifications) - .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(json_array)); - self.send(&response).await - } else { - Ok(()) - } - } - - async fn handle_read_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Some(other_id) = cv - .get_data(DataTypes::sender_id) - .as_number() - .map(|n| n as i64) - { - if sql::read_notification(user_id, other_id).await.is_ok() { - let response = CommunicationValue::new(CommunicationType::read_notification) - .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) - } - } else { - Ok(()) - } - } - - async fn handle_push_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Some(other_id) = cv - .get_data(DataTypes::sender_id) - .as_number() - .map(|n| n as i64) - { - if sql::add_notification(user_id, other_id).await.is_ok() { - let response = CommunicationValue::new(CommunicationType::push_notification) - .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) - } - } else { - Ok(()) - } - } - - async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - *self.ping.write().await = *last_ping; - } - - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); - self.send(&response).await - } - - // ------------------------------------------------------------------------- - // Utilities - // ------------------------------------------------------------------------- - - async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_out!(PrintType::Omikron, cv); - } - - let guard = self.sender.lock().await; - let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; - - sender - .send(cv) - .await - .map_err(|e| OmikronError::Send(e.to_string())) - } - - async fn send_error_response( - self: Arc, - message_id: u32, - error_type: CommunicationType, - ) -> OmikronResult<()> { - let error = CommunicationValue::new(error_type).with_id(message_id); - self.send(&error).await - } - - pub async fn close(self: Arc) { - log_in!( - self.get_omikron_id().await.unwrap_or(0), - PrintType::Omega, - "Omikron connection Closed" - ); - } - - async fn cleanup(self: Arc) { - if let Some(omikron_id) = self.state.read().await.omikron_id() { - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - - // Cancel cleanup task - if let Some(handle) = self.cleanup_handle.lock().await.take() { - handle.abort(); - } - } - - // Public API for external use - pub async fn is_authenticated(self: Arc) -> bool { - self.state.read().await.is_authenticated() - } - - pub async fn get_omikron_id(self: Arc) -> Option { - self.state.read().await.omikron_id() - } - - pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - self.send(cv).await - } -} - -// ============================================================================ -// Server Startup -// ============================================================================ - -pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - - let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; - log!("OmikronServer listening on port {}", port); - - while let Some((sender, mut receiver)) = host.next().await { - tokio::spawn(async move { - let conn = OmikronConnection::new(sender); - conn.handle(&mut receiver).await; - }); - } - - Ok(()) -} diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs deleted file mode 100644 index fbee4a9..0000000 --- a/src/transport/omikron_manager.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::transport::omikron_connection::OmikronConnection; -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::prelude::IteratorRandom; -use std::sync::Arc; - -pub static OMIKRON_CONNECTIONS: Lazy>> = - Lazy::new(|| DashMap::new()); - -pub async fn add_omikron(conn: Arc) { - let id = match conn.clone().get_omikron_id().await { - Some(id) => id, - _ => { - conn.close().await; - return; - } - }; - - if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { - old.close().await; - } -} - -pub async fn remove_omikron(omikron_id: i64) { - OMIKRON_CONNECTIONS.remove(&omikron_id); -} -pub async fn get_random_omikron() -> Result, ()> { - let mut rng = rand::thread_rng(); - - let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); - - if let Some(key) = keys.into_iter().choose(&mut rng) { - if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { - return Ok(entry.clone()); - } - } - - Err(()) -} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs old mode 100644 new mode 100755 index 68e3855..cb3ae6a --- a/src/util/crypto_helper.rs +++ b/src/util/crypto_helper.rs @@ -8,27 +8,36 @@ use sha2::{Digest, Sha256}; use x448::{PublicKey, Secret, SharedSecret}; /// Errors for crypto operations +#[allow(dead_code)] #[derive(Debug)] pub enum CryptoError { - Base64Decode, + Base64Decode(base64::DecodeError), + InvalidKey, AgreementError, EncryptionError(aes_gcm::Error), DecryptionError(aes_gcm::Error), } impl From for CryptoError { - fn from(_: base64::DecodeError) -> Self { - CryptoError::Base64Decode + fn from(err: base64::DecodeError) -> Self { + CryptoError::Base64Decode(err) } } -pub fn generate_keypair() -> (Secret, PublicKey) { +#[allow(dead_code)] +pub struct KeyPair { + pub secret: Secret, + pub public: PublicKey, +} + +#[allow(dead_code)] +pub fn generate_keypair() -> KeyPair { let mut buf = [0u8; 56]; let mut rng = OsRng; rng.fill_bytes(&mut buf); let secret = Secret::from_bytes(&buf).unwrap(); let public = PublicKey::from(&secret); - (secret, public) + KeyPair { secret, public } } pub fn public_key_to_base64(pubkey: &PublicKey) -> String { @@ -58,6 +67,7 @@ fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { key } +#[allow(dead_code)] pub fn encrypt_b64( base64_secret: &str, base64_peer_pub: &str, @@ -125,12 +135,14 @@ pub fn decrypt( Ok(plaintext) } +#[allow(dead_code)] pub fn hash_it(input: &str) -> Vec { let mut hasher = Sha256::new(); hasher.update(input.as_bytes()); hasher.finalize().to_vec() } +#[allow(dead_code)] pub fn hex_hash(input: &str) -> String { let digest = hash_it(input); digest.iter().map(|b| format!("{:02x}", b)).collect() diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs index 97acab4..bb784e8 100644 --- a/src/util/crypto_util.rs +++ b/src/util/crypto_util.rs @@ -4,11 +4,12 @@ use aes_gcm::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; use hkdf::Hkdf; -use sha2::{Digest, Sha256}; +type HkdfSha256 = sha2::Sha256; +use sha2::{Digest, Sha256 as HashSha256}; use x448::{PublicKey, Secret}; -// --- Custom Errors --- #[derive(Debug)] +#[allow(dead_code)] pub enum SecurePayloadError { InvalidBase64, InvalidHex, @@ -17,19 +18,16 @@ pub enum SecurePayloadError { InvalidKeyLength, } -// --- Data Format Enum --- #[derive(Clone, Copy, Debug)] +#[allow(dead_code)] pub enum DataFormat { Raw, Base64, Hex, } -// --- Main Class Structure --- pub struct SecurePayload { - /// The internal canonical representation is always raw bytes. inner_data: Vec, - /// The private key of the user associated with this payload instance. private_key: Secret, } @@ -42,8 +40,8 @@ impl Clone for SecurePayload { } } +#[allow(dead_code)] impl SecurePayload { - /// Clear Constructor: Takes data in any format and the user's private key. pub fn new>( data: T, format: DataFormat, @@ -68,12 +66,10 @@ impl SecurePayload { }) } - /// Helper to get the public key associated with this instance's private key. pub fn get_public_key(&self) -> [u8; 56] { *PublicKey::from(&self.private_key).as_bytes() } - /// Exports the internal data to the requested format pub fn export(&self, format: DataFormat) -> String { match format.into() { DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), @@ -82,14 +78,12 @@ impl SecurePayload { } } - /// Access raw bytes directly pub fn get_bytes(&self) -> &[u8] { &self.inner_data } - /// Returns the SHA-256 Hash of the data in the requested format pub fn get_hash(&self, format: DataFormat) -> String { - let mut hasher = Sha256::new(); + let mut hasher = HashSha256::new(); hasher.update(&self.inner_data); let result = hasher.finalize(); @@ -100,8 +94,6 @@ impl SecurePayload { } } - /// Encrypts the held data for a specific recipient using AES-256-GCM. - /// The message will contain ONLY the ciphertext. pub fn encrypt_x448(&self, public_key: S) -> Result where S: Into, @@ -109,22 +101,15 @@ impl SecurePayload { let peer_pub = public_key.into(); let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - println!( - "Encryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; - // 3. Key & Nonce Derivation (HKDF) - // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) .map_err(|_| SecurePayloadError::EncryptionError)?; let key = &okm[..32]; let nonce_bytes = &okm[32..]; - // 4. Encrypt with AES-256-GCM let cipher = Aes256Gcm::new(key.into()); let nonce = Nonce::from_slice(nonce_bytes); @@ -138,40 +123,33 @@ impl SecurePayload { ) .map_err(|_| SecurePayloadError::EncryptionError)?; - // 5. Result is ONLY the ciphertext. No key or nonce is packed. Ok(SecurePayload { inner_data: ciphertext, private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), }) } - /// Decrypts the held data providing the sender's public key manually. pub fn decrypt_to_format( &self, peer_public_key_bytes: &[u8; 56], output_format: DataFormat, ) -> Result { - let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; + let decrypted_instance = + self.decrypt_x448(PublicKey::from_bytes(peer_public_key_bytes).unwrap())?; Ok(decrypted_instance.export(output_format)) } - /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. - pub fn decrypt_x448( + pub fn decrypt_x448( &self, - peer_public_key_bytes: &[u8; 56], - ) -> Result { - // 1. Perform Exchange - let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); + peer_public_key_bytes: S, + ) -> Result + where + S: Into, + { + let peer_pub = peer_public_key_bytes.into(); let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - // LOGGING: Shared Secret - println!( - "Decryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 2. Key & Nonce Derivation (Must match encryption exactly) - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); let mut okm = [0u8; 44]; hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) .map_err(|_| SecurePayloadError::DecryptionError)?; @@ -179,7 +157,6 @@ impl SecurePayload { let key = &okm[..32]; let nonce_bytes = &okm[32..]; - // 3. Decrypt with AES-256-GCM let cipher = Aes256Gcm::new(key.into()); let nonce = Nonce::from_slice(nonce_bytes); diff --git a/src/util/file_util.rs b/src/util/file_util.rs index b108810..cd214b1 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -1,20 +1,9 @@ use std::fs::{self, File}; use std::io::{self, BufReader, Read}; use std::path::{Path, PathBuf}; -use tokio::io::AsyncWriteExt; -use uuid::Uuid; -use zip::ZipArchive; use crate::log; - -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() -} +use crate::util::logger::PrintType; #[allow(dead_code)] pub fn delete_directory(path: &str) -> bool { @@ -29,9 +18,11 @@ fn delete_dir_recursive(directory: &Path) -> bool { } if let Err(e) = fs::remove_dir_all(directory) { log!( + 0, + PrintType::General, "[IMPORTANT] Couldn't delete directory {}: {}", directory.display(), - e + e, ); return false; } @@ -39,13 +30,14 @@ fn delete_dir_recursive(directory: &Path) -> bool { } #[allow(dead_code)] -pub fn delete_user_directory(user_id: Uuid) { +pub fn delete_user_directory(user_id: i64) { let user_dir = Path::new(&get_directory()) .join("users") .join(user_id.to_string()); let _ = delete_dir_recursive(&user_dir); } +#[allow(dead_code)] pub fn load_file_buf(path: &str, name: &str) -> io::Result> { let dir = Path::new(&get_directory()).join(path); let file_path = dir.join(name); @@ -72,6 +64,7 @@ pub fn load_file_buf(path: &str, name: &str) -> io::Result> { let file = File::open(&file_path)?; Ok(BufReader::new(file)) } +#[allow(dead_code)] pub fn has_file(path: &str, name: &str) -> bool { let dir = Path::new(&get_directory()).join(path); let file_path = dir.join(name); @@ -86,6 +79,7 @@ pub fn has_file(path: &str, name: &str) -> bool { true } +#[allow(dead_code)] pub fn has_dir(path: &str) -> bool { let dir = Path::new(&get_directory()).join(path); @@ -96,13 +90,19 @@ pub fn has_dir(path: &str) -> bool { true } +#[allow(dead_code)] 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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create directories: {}", + e + ); return String::new(); } return String::new(); @@ -110,7 +110,12 @@ pub fn load_file(path: &str, name: &str) -> String { if !file_path.exists() { if let Err(e) = File::create(&file_path) { - log!("[IMPORTANT] Couldn't create file: {}", e); + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create file: {}", + e + ); } return String::new(); } @@ -129,19 +134,27 @@ pub fn load_file_vec(path: &str, name: &str) -> Result, std::io::Error> std::fs::read(file_path) } +#[allow(dead_code)] 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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create directories: {}", + e + ); return; } } if let Err(e) = fs::write(&file_path, value) { log!( + 0, + PrintType::General, "[IMPORTANT] Couldn't write file {}: {}", file_path.display(), e @@ -149,6 +162,7 @@ pub fn save_file(path: &str, name: &str, value: &str) { } } +#[allow(dead_code)] pub fn get_children(path: &str) -> Vec { let dir = Path::new(&get_directory()).join(path); let mut children = Vec::new(); @@ -169,121 +183,3 @@ pub fn get_directory() -> String { .to_string_lossy() .to_string() } - -// Helper to download the zip file content to a file on disk -#[allow(dead_code)] -async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { - let response = reqwest::get(url).await?; - - // Check for successful response status - if !response.status().is_success() { - return Err(format!("Failed to download file: Status {}", response.status()).into()); - } - - let mut zip_file = tokio::fs::File::create(as_name).await?; - let body = response.bytes().await?; - zip_file.write_all(&body).await?; - - Ok(()) -} - -#[allow(dead_code, deprecated)] -fn extract_zip_contents_to_folder( - zip_path: &Path, - target_dir: &Path, -) -> Result<(), Box> { - let file = File::open(zip_path)?; - let mut archive = ZipArchive::new(file)?; - - let staging_dir = target_dir.with_extension("staging"); - - let _ = fs::remove_dir_all(&staging_dir); - fs::create_dir_all(&staging_dir)?; - - let mut first_item_name: Option = None; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - let entry_path = staging_dir.join(file.sanitized_name()); - - if i == 0 { - if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { - first_item_name = Some(file.sanitized_name()); - } - } - - if file.name().ends_with('/') { - fs::create_dir_all(&entry_path)?; - } else { - if let Some(parent) = entry_path.parent() { - fs::create_dir_all(parent)?; - } - let mut out_file = File::create(entry_path)?; - io::copy(&mut file, &mut out_file)?; - } - } - - if let Some(root_path) = first_item_name { - let root_dir = staging_dir.join(&root_path); - - if root_dir.is_dir() { - let root_contents_count = fs::read_dir(&staging_dir)?.count(); - - if root_contents_count == 1 - || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) - { - let _ = fs::remove_dir_all(target_dir); - fs::create_dir_all(target_dir)?; - - for entry in fs::read_dir(root_dir)? { - let entry = entry?; - let src = entry.path(); - let dest = target_dir.join(entry.file_name()); - - if let Err(_) = fs::rename(&src, &dest) { - if src.is_file() { - fs::copy(&src, &dest)?; - } else { - if entry.path().is_dir() { - fs::rename(&src, &dest)?; - } - } - } - } - - let _ = fs::remove_dir_all(&staging_dir); - return Ok(()); - } - } - } - - log!("Extracting directly (no single root folder detected)."); - let _ = fs::remove_dir_all(target_dir); - fs::rename(&staging_dir, target_dir)?; - - Ok(()) -} - -#[allow(dead_code)] -pub async fn download_and_extract_zip(url: &str, as_name: &str) { - let base_dir = PathBuf::from(get_directory()); - let zip_filename = format!("{}.zip", Uuid::new_v4()); - let zip_path = base_dir.join(&zip_filename); - let target_dir = base_dir.join(as_name); - - if let Err(e) = download_zip(url, &zip_path).await { - log!("Error downloading file: {}", e); - return; - } - - let zip_path_clone = zip_path.clone(); - let target_dir_clone = target_dir.clone(); - let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone); - if let Err(e) = extract_result { - log!("Panic during ZIP extraction: {}", e); - } - - if let Err(e) = tokio::fs::remove_file(&zip_path).await { - log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index d1a7764..9e9a085 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -9,13 +9,12 @@ use std::{ }; use ansi_term::Color; -use epsilon_core::{CommunicationValue, DataTypes, DataValue}; -use json::JsonValue; +use ttp_core::{CommunicationValue, DataTypes, DataValue}; static LOGGER: OnceLock> = OnceLock::new(); +#[allow(dead_code)] #[derive(Clone, Copy)] -#[allow(unused)] pub enum PrintType { Call, Client, @@ -96,12 +95,14 @@ fn fixed_box(content: &str, width: usize) -> String { } pub fn log_internal( - sender: Option, + sender: i64, kind: PrintType, prefix: &'static str, is_error: bool, message: String, ) { + let sender = if sender == 0 { None } else { Some(sender) }; + if let Some(tx) = LOGGER.get() { let _ = tx.send(LogMessage { timestamp_ms: SystemTime::now() @@ -116,102 +117,28 @@ pub fn log_internal( }); } } - #[macro_export] macro_rules! log { - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - "", - false, - format!($($arg)*) - ) - }; - - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) + ($sender: expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, "", false, format!($($arg)*)) }; } - #[macro_export] macro_rules! log_in { - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) - }; - - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - ">", - false, - format!($($arg)*) - ) + ($sender: expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, ">", false, format!($($arg)*)) }; } - #[macro_export] macro_rules! log_out { - - // sender + actor ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) - }; - - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - "<", - false, - format!($($arg)*) - ) + $crate::util::logger::log_internal($sender, $kind, "<", false, format!($($arg)*)) }; } - #[macro_export] macro_rules! log_err { - - // sender + actor ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) - }; - - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - ">>", - true, - format!($($arg)*) - ) + $crate::util::logger::log_internal($sender, $kind, ">>", true, format!($($arg)*)) }; } @@ -224,7 +151,7 @@ pub fn log_cv_internal( let formatted = format_cv(cv); log_internal( - Some(cv.get_sender() as i64), + cv.get_sender() as i64, print_type.unwrap_or(PrintType::General), prefix, false, @@ -322,6 +249,7 @@ fn format_array(arr: Vec) -> String { parts.join(", ") } + #[macro_export] macro_rules! log_cv { ($kind:expr, $cv:expr) => { From b23eff24a384b1d1ef561928ed971dc82e9dc8c9 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 2 Apr 2026 22:25:10 +0200 Subject: [PATCH 100/220] Sync --- .gitignore | 16 +- Cargo.lock | 2527 ++++++----------- Cargo.toml | 38 +- README.md | 3 +- .../anonymous_client_connection.rs | 519 ++++ src/anonymous_clients/anonymous_manager.rs | 53 + src/anonymous_clients/mod.rs | 2 + src/calls/call_group.rs | 106 + src/calls/call_manager.rs | 97 + src/calls/call_util.rs | 151 + src/calls/caller.rs | 49 + src/calls/mod.rs | 4 + src/data/mod.rs | 1 + .../connection_status.rs => data/user.rs} | 0 src/main.rs | 56 +- src/omega/mod.rs | 1 + src/omega/omega_connection.rs | 741 +++++ src/omega/ping_pong_task.rs | 41 + src/rho/client_connection.rs | 583 ++++ src/rho/connection.rs | 358 +++ src/rho/iota_connection.rs | 461 +++ src/rho/mod.rs | 6 + src/rho/rho_connection.rs | 199 ++ src/rho/rho_manager.rs | 83 + src/rho/server.rs | 25 + src/server/api.rs | 275 -- src/server/mod.rs | 3 - src/server/server.rs | 67 - src/server/short_link.rs | 87 - src/sql/mod.rs | 3 - src/sql/sql.rs | 765 ----- src/sql/user_online_tracker.rs | 144 - src/transport/mod.rs | 2 - src/transport/omikron_connection.rs | 1139 -------- src/transport/omikron_manager.rs | 39 - src/util/crypto_helper.rs | 22 +- src/util/crypto_util.rs | 59 +- src/util/file_util.rs | 166 +- src/util/logger.rs | 100 +- 39 files changed, 4548 insertions(+), 4443 deletions(-) mode change 100755 => 100644 Cargo.toml create mode 100644 src/anonymous_clients/anonymous_client_connection.rs create mode 100644 src/anonymous_clients/anonymous_manager.rs create mode 100644 src/anonymous_clients/mod.rs create mode 100644 src/calls/call_group.rs create mode 100644 src/calls/call_manager.rs create mode 100644 src/calls/call_util.rs create mode 100644 src/calls/caller.rs create mode 100644 src/calls/mod.rs create mode 100644 src/data/mod.rs rename src/{sql/connection_status.rs => data/user.rs} (100%) create mode 100644 src/omega/mod.rs create mode 100644 src/omega/omega_connection.rs create mode 100644 src/omega/ping_pong_task.rs create mode 100644 src/rho/client_connection.rs create mode 100755 src/rho/connection.rs create mode 100755 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/rho/server.rs delete mode 100644 src/server/api.rs delete mode 100644 src/server/mod.rs delete mode 100644 src/server/server.rs delete mode 100644 src/server/short_link.rs delete mode 100644 src/sql/mod.rs delete mode 100644 src/sql/sql.rs delete mode 100644 src/sql/user_online_tracker.rs delete mode 100644 src/transport/mod.rs delete mode 100644 src/transport/omikron_connection.rs delete mode 100644 src/transport/omikron_manager.rs mode change 100644 => 100755 src/util/crypto_helper.rs diff --git a/.gitignore b/.gitignore index e3d273c..1baef92 100644 --- a/.gitignore +++ b/.gitignore @@ -8,22 +8,12 @@ target # MSVC Windows builds of rustc generate these, which store debugging information *.pdb - +*.env # 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/ - - -.env -/logs # Added by cargo - /target +/logs diff --git a/Cargo.lock b/Cargo.lock index 1444c40..41cef35 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,248 +3,35 @@ version = 4 [[package]] -name = "Omega" +name = "Omikron" version = "0.1.0" dependencies = [ - "actix-web", "aes-gcm", "ansi_term", - "anyhow", - "base64", + "base64 0.22.1", "dashmap", "dotenv", - "epsilon-core", - "epsilon-native", + "futures", "hex", "hkdf", - "json", + "livekit-api", + "livekit-protocol", + "log", "once_cell", "rand 0.8.5", "rand_core 0.6.4", - "reqwest", "rustls", - "rustls-pemfile", "sha2", - "sqlx", - "strum 0.27.2", - "strum_macros 0.27.2", + "strum", + "strum_macros", "thiserror 2.0.18", "tokio", + "ttp-core", + "ttp-native", "uuid", "x448", - "zip", ] -[[package]] -name = "actix-codec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" -dependencies = [ - "bitflags 2.11.0", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "base64", - "bitflags 2.11.0", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "foldhash", - "futures-core", - "h2 0.3.27", - "http 0.2.12", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.9.2", - "sha1", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" -dependencies = [ - "bytestring", - "cfg-if", - "http 0.2.12", - "regex", - "regex-lite", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.10", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "impl-more", - "pin-project-lite", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-tls", - "actix-utils", - "actix-web-codegen", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "foldhash", - "futures-core", - "futures-util", - "impl-more", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "regex-lite", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.6.3", - "time", - "tracing", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" @@ -290,26 +77,14 @@ dependencies = [ ] [[package]] -name = "alloc-no-stdlib" -version = "2.0.4" +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ - "alloc-no-stdlib", + "libc", ] -[[package]] -name = "allocator-api2" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" - [[package]] name = "ansi_term" version = "0.12.1" @@ -325,15 +100,6 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "asn1-rs" version = "0.7.1" @@ -404,8 +170,8 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", - "futures-lite 2.6.1", + "fastrand", + "futures-lite", "pin-project-lite", "slab", ] @@ -418,33 +184,13 @@ checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" dependencies = [ "async-channel 2.5.0", "async-executor", - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "blocking", - "futures-lite 2.6.1", + "futures-lite", "once_cell", ] -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2 0.4.10", - "waker-fn", -] - [[package]] name = "async-io" version = "2.6.0" @@ -455,23 +201,14 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "parking", - "polling 3.11.0", - "rustix 1.1.4", + "polling", + "rustix", "slab", "windows-sys 0.61.2", ] -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - [[package]] name = "async-lock" version = "3.4.2" @@ -483,6 +220,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 1.0.69", + "url", +] + [[package]] name = "async-std" version = "1.13.2" @@ -491,13 +240,13 @@ checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" dependencies = [ "async-channel 1.9.0", "async-global-executor", - "async-io 2.6.0", - "async-lock 3.4.2", + "async-io", + "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "gloo-timers", "kv-log-macro", "log", @@ -516,12 +265,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] -name = "atoi" -version = "2.0.0" +name = "async-tungstenite" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" dependencies = [ - "num-traits", + "async-native-tls", + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.21.0", ] [[package]] @@ -538,9 +293,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -549,9 +304,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -559,6 +314,18 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64" version = "0.22.1" @@ -571,20 +338,11 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" -dependencies = [ - "serde_core", -] [[package]] name = "block-buffer" @@ -595,6 +353,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + [[package]] name = "blocking" version = "1.6.2" @@ -604,31 +371,10 @@ dependencies = [ "async-channel 2.5.0", "async-task", "futures-io", - "futures-lite 2.6.1", + "futures-lite", "piper", ] -[[package]] -name = "brotli" -version = "8.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bumpalo" version = "3.20.2" @@ -647,29 +393,11 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" -[[package]] -name = "bytestring" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" -dependencies = [ - "bytes", -] - -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - [[package]] name = "cc" -version = "1.2.57" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "jobserver", @@ -677,12 +405,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -695,6 +417,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "num-traits", +] + [[package]] name = "cipher" version = "0.4.4" @@ -707,23 +438,13 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -739,42 +460,6 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[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" version = "0.10.1" @@ -800,45 +485,24 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" -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 = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.7" @@ -859,6 +523,33 @@ dependencies = [ "cipher", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.1.0" @@ -879,12 +570,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" -[[package]] -name = "deflate64" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" - [[package]] name = "der" version = "0.7.10" @@ -919,40 +604,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", -] - [[package]] name = "digest" version = "0.10.7" @@ -965,6 +616,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -982,25 +643,57 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "dunce" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "ed448-goldilocks" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" dependencies = [ - "fiat-crypto", + "fiat-crypto 0.1.20", "hex", "subtle", ] @@ -1010,43 +703,26 @@ name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -dependencies = [ - "serde", -] [[package]] -name = "encoding_rs" -version = "0.8.35" +name = "elliptic-curve" +version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ - "cfg-if", -] - -[[package]] -name = "epsilon-core" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "base64", - "byteorder", - "rand 0.8.5", - "strum 0.28.0", - "strum_macros 0.28.0", -] - -[[package]] -name = "epsilon-native" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "epsilon-core", - "quinn", - "rustls", - "rustls-native-certs", - "thiserror 2.0.18", - "tokio", - "wtransport", + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", ] [[package]] @@ -1065,17 +741,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "etcetera" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" -dependencies = [ - "cfg-if", - "home", - "windows-sys 0.48.0", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -1103,27 +768,34 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "fiat-crypto" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1131,26 +803,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] -name = "flate2" -version = "1.1.9" +name = "fixedbitset" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", - "zlib-rs", -] - -[[package]] -name = "flume" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" -dependencies = [ - "futures-core", - "futures-sink", - "spin", -] +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fnv" @@ -1164,6 +820,21 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[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" @@ -1179,6 +850,21 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" version = "0.3.32" @@ -1206,51 +892,36 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-intrusive" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" -dependencies = [ - "futures-core", - "lock_api", - "parking_lot", -] - [[package]] name = "futures-io" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - [[package]] name = "futures-lite" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand", "futures-core", "futures-io", "parking", "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -1269,8 +940,10 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1286,6 +959,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1351,41 +1025,14 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.27" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "h2" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.4.0", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", + "ff", + "rand_core 0.6.4", + "subtle", ] [[package]] @@ -1400,8 +1047,6 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "allocator-api2", - "equivalent", "foldhash", ] @@ -1412,13 +1057,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] -name = "hashlink" -version = "0.10.0" +name = "heck" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" -dependencies = [ - "hashbrown 0.15.5", -] +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "heck" @@ -1426,12 +1068,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - [[package]] name = "hermit-abi" version = "0.5.2" @@ -1462,15 +1098,6 @@ dependencies = [ "digest", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "httlib-huffman" version = "0.3.4" @@ -1527,47 +1154,39 @@ 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.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", - "h2 0.4.13", "http 1.4.0", "http-body", "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", ] [[package]] -name = "hyper-rustls" -version = "0.27.7" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "http 1.4.0", + "bytes", + "http-body-util", "hyper", "hyper-util", - "rustls", - "rustls-pki-types", + "native-tls", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower-service", ] @@ -1577,7 +1196,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -1589,21 +1208,20 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2 0.6.3", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", ] [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1611,9 +1229,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1624,9 +1242,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1638,15 +1256,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1658,15 +1276,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1704,12 +1322,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "impl-more" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" - [[package]] name = "indexmap" version = "2.13.0" @@ -1731,26 +1343,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -1759,41 +1351,37 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", ] [[package]] -name = "itoa" -version = "1.0.17" +name = "itertools" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", + "either", ] [[package]] -name = "jni-sys" -version = "0.3.0" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" @@ -1807,19 +1395,36 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] [[package]] -name = "json" -version = "0.12.4" +name = "jsonwebtoken" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "base64 0.22.1", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac", + "js-sys", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde_json", + "sha2", + "signature", +] [[package]] name = "kv-log-macro" @@ -1830,12 +1435,6 @@ dependencies = [ "log", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.5.0" @@ -1851,17 +1450,11 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" -[[package]] -name = "libbz2-rs-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" - [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" @@ -1869,34 +1462,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" -dependencies = [ - "bitflags 2.11.0", - "libc", - "plain", - "redox_syscall 0.7.3", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" -dependencies = [ - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1905,26 +1470,63 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] -name = "local-channel" -version = "0.1.5" +name = "livekit-api" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +checksum = "020c7eaab7cec16b69a058659f9acff32067e0e2d4f81e80639f3ffa29464b09" dependencies = [ - "futures-core", - "futures-sink", - "local-waker", + "async-tungstenite", + "base64 0.21.7", + "http 1.4.0", + "jsonwebtoken", + "livekit-protocol", + "log", + "os_info", + "parking_lot", + "pbjson-types", + "prost", + "rand 0.9.2", + "reqwest", + "scopeguard", + "serde", + "serde_json", + "sha2", + "thiserror 1.0.69", + "tokio-tungstenite", + "url", ] [[package]] -name = "local-waker" -version = "0.1.4" +name = "livekit-protocol" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" +checksum = "c21d56b4dc2598b1296ca891abb225bf1c05ea007d6f43bc4fd48c31be64bf59" +dependencies = [ + "futures-util", + "livekit-runtime", + "parking_lot", + "pbjson", + "pbjson-types", + "prost", + "serde", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "livekit-runtime" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532e84c6cdc5fe774f2b5d9912597b5f3bea561927a48296d03e24549d21c3f6" +dependencies = [ + "tokio", + "tokio-stream", +] [[package]] name = "lock_api" @@ -1950,38 +1552,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-rust2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" -dependencies = [ - "crc", - "sha2", -] - -[[package]] -name = "md-5" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" -dependencies = [ - "cfg-if", - "digest", -] - [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1989,25 +1565,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] -name = "miniz_oxide" -version = "0.8.9" +name = "mio" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ - "adler2", - "simd-adler32", + "libc", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "mio" -version = "1.1.1" +name = "multimap" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", - "wasi", - "windows-sys 0.61.2", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", ] [[package]] @@ -2048,9 +1648,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -2082,6 +1682,165 @@ dependencies = [ "libm", ] +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + [[package]] name = "octets" version = "0.3.5" @@ -2109,12 +1868,90 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +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.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_info" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "parking" version = "2.2.1" @@ -2139,19 +1976,46 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] [[package]] -name = "pbkdf2" -version = "0.12.2" +name = "pbjson" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" dependencies = [ - "digest", - "hmac", + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck 0.4.1", + "itertools 0.11.0", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", ] [[package]] @@ -2160,7 +2024,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -2179,6 +2043,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2198,7 +2072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand", "futures-io", ] @@ -2229,28 +2103,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - [[package]] name = "polling" version = "3.11.0" @@ -2259,9 +2111,9 @@ checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ "cfg-if", "concurrent-queue", - "hermit-abi 0.5.2", + "hermit-abi", "pin-project-lite", - "rustix 1.1.4", + "rustix", "windows-sys 0.61.2", ] @@ -2279,9 +2131,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2292,12 +2144,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppmd-rust" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2317,6 +2163,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2326,6 +2181,59 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quinn" version = "0.11.9" @@ -2487,16 +2395,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", -] - -[[package]] -name = "redox_syscall" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" -dependencies = [ - "bitflags 2.11.0", + "bitflags", ] [[package]] @@ -2522,12 +2421,6 @@ dependencies = [ "regex-syntax", ] -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - [[package]] name = "regex-syntax" version = "0.8.10" @@ -2536,33 +2429,31 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.2" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "base64", + "base64 0.22.1", "bytes", - "encoding_rs", "futures-core", - "h2 0.4.13", "http 1.4.0", "http-body", "http-body-util", "hyper", - "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", - "mime", + "native-tls", "percent-encoding", "pin-project-lite", - "quinn", - "rustls", "rustls-pki-types", - "rustls-platform-verifier", + "serde", + "serde_json", + "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-rustls", + "tokio-native-tls", "tower", "tower-http", "tower-service", @@ -2572,6 +2463,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2608,9 +2509,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -2630,30 +2531,16 @@ dependencies = [ "nom", ] -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags", "errno", "libc", - "linux-raw-sys 0.12.1", + "linux-raw-sys", "windows-sys 0.61.2", ] @@ -2685,15 +2572,6 @@ dependencies = [ "security-framework", ] -[[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.14.0" @@ -2704,38 +2582,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -2755,15 +2606,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[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.29" @@ -2779,14 +2621,28 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.10.1", + "bitflags", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -2911,12 +2767,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "simd-adler32" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" - [[package]] name = "slab" version = "0.4.12" @@ -2928,19 +2778,6 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" -dependencies = [ - "serde", -] - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] [[package]] name = "socket2" @@ -2967,9 +2804,6 @@ name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" -dependencies = [ - "lock_api", -] [[package]] name = "spki" @@ -2981,242 +2815,25 @@ dependencies = [ "der", ] -[[package]] -name = "sqlx" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" -dependencies = [ - "sqlx-core", - "sqlx-macros", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", -] - -[[package]] -name = "sqlx-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" -dependencies = [ - "async-io 1.13.0", - "async-std", - "base64", - "bytes", - "crc", - "crossbeam-queue", - "either", - "event-listener 5.4.1", - "futures-core", - "futures-intrusive", - "futures-io", - "futures-util", - "hashbrown 0.15.5", - "hashlink", - "indexmap", - "log", - "memchr", - "once_cell", - "percent-encoding", - "serde", - "serde_json", - "sha2", - "smallvec", - "thiserror 2.0.18", - "tracing", - "url", -] - -[[package]] -name = "sqlx-macros" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" -dependencies = [ - "proc-macro2", - "quote", - "sqlx-core", - "sqlx-macros-core", - "syn", -] - -[[package]] -name = "sqlx-macros-core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" -dependencies = [ - "async-std", - "dotenvy", - "either", - "heck", - "hex", - "once_cell", - "proc-macro2", - "quote", - "serde", - "serde_json", - "sha2", - "sqlx-core", - "sqlx-mysql", - "sqlx-postgres", - "sqlx-sqlite", - "syn", - "url", -] - -[[package]] -name = "sqlx-mysql" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" -dependencies = [ - "atoi", - "base64", - "bitflags 2.11.0", - "byteorder", - "bytes", - "crc", - "digest", - "dotenvy", - "either", - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "generic-array", - "hex", - "hkdf", - "hmac", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "percent-encoding", - "rand 0.8.5", - "rsa", - "serde", - "sha1", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-postgres" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" -dependencies = [ - "atoi", - "base64", - "bitflags 2.11.0", - "byteorder", - "crc", - "dotenvy", - "etcetera", - "futures-channel", - "futures-core", - "futures-util", - "hex", - "hkdf", - "hmac", - "home", - "itoa", - "log", - "md-5", - "memchr", - "once_cell", - "rand 0.8.5", - "serde", - "serde_json", - "sha2", - "smallvec", - "sqlx-core", - "stringprep", - "thiserror 2.0.18", - "tracing", - "whoami", -] - -[[package]] -name = "sqlx-sqlite" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" -dependencies = [ - "atoi", - "flume", - "futures-channel", - "futures-core", - "futures-executor", - "futures-intrusive", - "futures-util", - "libsqlite3-sys", - "log", - "percent-encoding", - "serde", - "serde_urlencoded", - "sqlx-core", - "thiserror 2.0.18", - "tracing", - "url", -] - [[package]] name = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" -[[package]] -name = "stringprep" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" -dependencies = [ - "unicode-bidi", - "unicode-normalization", - "unicode-properties", -] - -[[package]] -name = "strum" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" - [[package]] name = "strum" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -[[package]] -name = "strum_macros" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "strum_macros" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -3260,24 +2877,16 @@ dependencies = [ ] [[package]] -name = "system-configuration" -version = "0.7.0" +name = "tempfile" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.9.4", - "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", + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -3353,9 +2962,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3405,28 +3014,40 @@ dependencies = [ ] [[package]] -name = "tokio-rustls" -version = "0.26.4" +name = "tokio-native-tls" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ - "rustls", + "native-tls", "tokio", ] [[package]] -name = "tokio-util" -version = "0.7.18" +name = "tokio-stream" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ - "bytes", "futures-core", - "futures-sink", "pin-project-lite", "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.20.1", +] + [[package]] name = "tower" version = "0.5.3" @@ -3448,7 +3069,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags", "bytes", "futures-util", "http 1.4.0", @@ -3478,7 +3099,6 @@ version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ - "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -3510,45 +3130,84 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttp-core" +version = "0.1.0" +source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" +dependencies = [ + "base64 0.22.1", + "byteorder", + "rand 0.8.5", + "strum", + "strum_macros", +] + +[[package]] +name = "ttp-native" +version = "0.1.0" +source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" +dependencies = [ + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "ttp-core", + "wtransport", +] + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" -[[package]] -name = "unicode-bidi" -version = "0.3.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" - [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-normalization" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-properties" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - [[package]] name = "unicode-xid" version = "0.2.6" @@ -3589,6 +3248,12 @@ dependencies = [ "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" @@ -3597,9 +3262,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3624,22 +3289,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -3673,17 +3322,11 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -3694,23 +3337,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3718,9 +3357,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -3731,9 +3370,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -3766,7 +3405,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags", "hashbrown 0.15.5", "indexmap", "semver", @@ -3774,9 +3413,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -3792,25 +3431,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "whoami" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] - [[package]] name = "winapi" version = "0.3.9" @@ -3827,15 +3447,6 @@ 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.2", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3848,53 +3459,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" @@ -3922,36 +3486,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -3985,18 +3519,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4009,18 +3531,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4033,18 +3543,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4069,18 +3567,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4093,18 +3579,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4117,18 +3591,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4141,18 +3603,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4181,7 +3631,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "wit-parser", ] @@ -4192,7 +3642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", - "heck", + "heck 0.5.0", "indexmap", "prettyplease", "syn", @@ -4223,7 +3673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags", "indexmap", "log", "serde", @@ -4352,9 +3802,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4363,9 +3813,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4375,18 +3825,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4395,18 +3845,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4419,26 +3869,12 @@ name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4447,9 +3883,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4458,90 +3894,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "zip" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" -dependencies = [ - "aes", - "arbitrary", - "bzip2", - "constant_time_eq", - "crc32fast", - "deflate64", - "flate2", - "getrandom 0.3.4", - "hmac", - "indexmap", - "lzma-rust2", - "memchr", - "pbkdf2", - "ppmd-rust", - "sha1", - "time", - "zeroize", - "zopfli", - "zstd", -] - -[[package]] -name = "zlib-rs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" - [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" - -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml old mode 100755 new mode 100644 index 34673fb..3127b12 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,39 +1,37 @@ [package] -name = "Omega" +name = "Omikron" version = "0.1.0" edition = "2024" [dependencies] -epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } -epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } +ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" } +ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" } + +ansi_term = "*" +uuid = { version = "*", features = ["v4"] } -actix-web = { version = "4.12.1", features = ["rustls-0_23"] } -aes-gcm = "*" -ansi_term = "0.12.1" -anyhow = "1.0.101" base64 = "0.22.1" -dashmap = "6.1.0" -dotenv = "0.15.0" -hex = "0.4.3" -hkdf = "0.12.4" -json = "0.12.4" +dashmap = "*" +futures = "*" +hex = "*" once_cell = "1.21.3" rand = "0.8" rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = { version = "0.13.2" } rustls = { version = "0.23.37", default-features = false, features = [ "std", "tls12", "aws-lc-rs", "prefer-post-quantum", ] } -rustls-pemfile = "2.2.0" +aes-gcm = "0.10.3" sha2 = "0.10.9" -sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } -strum = "0.27.2" -strum_macros = "0.27.2" tokio = { version = "*", features = ["full"] } -uuid = { version = "1.19.0", features = ["v4"] } -x448 = "0.6.0" -zip = "6.0.0" +x448 = { version = "*" } +log = "0.4" +dotenv = "0.15.0" +hkdf = "0.12.4" +strum = "0.28.0" +strum_macros = "0.28.0" +livekit-api = { version = "0.4.14", features = ["native-tls"] } +livekit-protocol = "0.7.1" thiserror = "2.0.18" diff --git a/README.md b/README.md index a2c50d5..06c1cdb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1 @@ -# Omega -A loadballancer and Omikron-manager for Tensamin +# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omikron) ⚠️ diff --git a/src/anonymous_clients/anonymous_client_connection.rs b/src/anonymous_clients/anonymous_client_connection.rs new file mode 100644 index 0000000..8c57490 --- /dev/null +++ b/src/anonymous_clients/anonymous_client_connection.rs @@ -0,0 +1,519 @@ +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +use crate::anonymous_clients::anonymous_manager::{self, generate_username}; +use crate::calls::call_manager; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::rho::rho_manager; +use crate::util::logger::PrintType; +use crate::{log_cv_in, log_cv_out, log_out}; + +pub struct AnonymousClientConnection { + user_id: u64, + + pub sender: Arc, + pub receiver: Arc, + pub ping: Arc>, + pub interested_users: Arc>>, + is_open: Arc>, + pub user_name: Arc>, + pub display_name: Arc>, + pub avatar: Arc>, +} + +impl AnonymousClientConnection { + pub async fn from_general(general: Arc, user_id: u64) -> Arc { + let username: String = generate_username(); + Arc::new(Self { + user_id: user_id, + + ping: Arc::new(RwLock::new(0)), + interested_users: Arc::new(RwLock::new(Vec::new())), + is_open: Arc::new(RwLock::new(true)), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + user_name: Arc::new(RwLock::new(username.to_lowercase())), + display_name: Arc::new(RwLock::new(username)), + avatar: Arc::new(RwLock::new(String::new())), + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + while let Ok(cv) = self_clone.receiver.receive().await { + self_clone.clone().handle_message(cv).await; + } + self_clone.handle_close().await; + }); + } + + /// Get the user ID + pub fn get_user_id(&self) -> u64 { + self.user_id + } + + /// Get the user name + pub async fn get_user_name(&self) -> String { + self.user_name.read().await.clone() + } + + /// Get the display name + pub async fn get_display_name(&self) -> String { + self.display_name.read().await.clone() + } + + pub async fn set_display_name(&self, display: String) { + *self.display_name.write().await = display; + } + + /// Get the avatar + pub async fn get_avatar(&self) -> String { + self.avatar.read().await.clone() + } + + /// Send a CommunicationValue to the client + pub async fn send_message(self: Arc, cv: &CommunicationValue) { + if !*self.is_open.read().await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Attempted to send message to a closed connection." + ); + return; + } + if !cv.is_type(CommunicationType::pong) { + log_cv_out!(PrintType::Client, &cv); + } + if let Err(e) = self.sender.send(&cv).await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Send failed: {:?}", + e + ); + } + } + + /// Handle incoming message from client + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + tokio::spawn(async move { + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_cv_in!(PrintType::Client, &cv); + + if cv.is_type(CommunicationType::identification) { + let call_id = + Uuid::parse_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")) + .unwrap_or(Uuid::new_v4()); + + let call = if let Some(call) = call_manager::get_call(call_id).await { + if call.is_anonymous().await { + call + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + return; + } + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + return; + }; + + let mut invited = Vec::new(); + for call_invitee in call.members.read().await.clone() { + let call_invitee_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::get_user_data).add_data( + DataTypes::user_id, + DataValue::Number(call_invitee.user_id as i64), + ), + Some(Duration::from_secs(2)), + ) + .await + .unwrap(); + let mut json_invitee = Vec::new(); + let _ = json_invitee.push(( + DataTypes::user_id, + call_invitee_cv.get_data(DataTypes::user_id).clone(), + )); + let _ = json_invitee.push(( + DataTypes::username, + call_invitee_cv.get_data(DataTypes::username).clone(), + )); + let _ = json_invitee.push(( + DataTypes::display, + call_invitee_cv.get_data(DataTypes::display).clone(), + )); + let _ = json_invitee.push(( + DataTypes::avatar, + call_invitee_cv.get_data(DataTypes::avatar).clone(), + )); + + let _ = invited.push(DataValue::Container(json_invitee)); + } + + let token = call.create_anonymous_token(self.get_user_id()).await; + + let mut serialized = Vec::new(); + let _ = serialized.push((DataTypes::call_id, DataValue::Str(call_id.to_string()))); + let _ = + serialized.push((DataTypes::call_invited, DataValue::Array(invited.clone()))); + let _ = serialized.push((DataTypes::call_members, DataValue::Array(invited))); + let _ = serialized.push((DataTypes::call_token, DataValue::Str(token.unwrap()))); + self.clone() + .send_message( + &&CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, DataValue::Number(self.user_id as i64)) + .add_data( + DataTypes::username, + DataValue::Str(self.clone().get_user_name().await), + ) + .add_data( + DataTypes::display, + DataValue::Str(self.get_display_name().await), + ) + .add_data(DataTypes::avatar, DataValue::Str(self.get_avatar().await)) + .add_data(DataTypes::call_state, DataValue::Container(serialized)), + ) + .await; + } + + // 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::call_token) { + self.handle_get_call(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_disconnect_user) { + self.handle_call_disconnect_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_timeout_user) { + self.handle_call_timeout_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::change_user_data) { + if let Some(display_name) = cv.get_data(DataTypes::display).as_str() { + let _ = self.set_display_name(display_name.to_string()).await; + } + + return; + } + + if cv.is_type(CommunicationType::get_user_data) { + if let Some(anonymous) = { + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + anonymous_manager::get_anonymous_user(user_id as u64).await + } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { + anonymous_manager::get_anonymous_user_by_name(username.to_string()).await + } else { + None + } + } { + let response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data( + DataTypes::username, + DataValue::Str(anonymous.get_user_name().await), + ) + .add_data( + DataTypes::user_id, + DataValue::Number(anonymous.user_id as i64), + ) + .add_data( + DataTypes::display, + DataValue::Str(anonymous.get_display_name().await), + ) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())) + .add_data( + DataTypes::avatar, + DataValue::Str(anonymous.get_avatar().await), + ); + + self.send_message(&response).await; + + return; + } + } + + if cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::delete_user) + { + self.handle_omega_forward(cv).await; + return; + } + }); + } + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let client_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + client_for_closure.send_message(&response_cv).await; + } + }); + } + + /// Handle ping message + async fn handle_ping(self: Arc, cv: CommunicationValue) { + // Update our ping if provided + if let DataValue::Number(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; + } + } + + // Send pong response + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + + self.send_message(&response).await; + } + + /// Handle client status change + async fn handle_client_changed(self: Arc, _cv: CommunicationValue) { + /*let user_id = self.get_user_id().await; + if let Some(_status_str) = cv.get_data(DataTypes::user_state) { + let user_status = UserStatus::online; + }*/ + } + + /// Handle call invite + async fn handle_call_invite(self: Arc, cv: CommunicationValue) { + let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); + if receiver_id == 0 { + self.send_error_response(&cv.get_id(), CommunicationType::error_no_user_id) + .await; + return; + } + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_call_id, + ) + .await; + return; + } + }, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error_no_call_id) + .await; + return; + } + }; + + let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; + if !invited { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + + // Find target RhoConnection + let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { + Some(rho) => rho, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Get sender user ID + let sender_id = self.get_user_id(); + + // Create and send call distribution message + let forward = CommunicationValue::new(CommunicationType::call_invite) + .with_receiver(receiver_id as u64) + .with_sender(sender_id) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data( + DataTypes::receiver_id, + DataValue::Str(receiver_id.to_string()), + ) + .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); + + target_rho.message_to_client(forward).await; + + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle get call request + async fn handle_get_call(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id(); + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(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; + } + }, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + if let Some(token) = call_manager::get_call_token(user_id, call_id).await { + let response = CommunicationValue::new(CommunicationType::call_token) + .with_id(cv.get_id()) + .with_receiver(user_id) + .add_data(DataTypes::call_token, DataValue::Str(token.to_string())); + self.send_message(&response).await; + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + } + async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id()) + .await + .unwrap() + .has_admin() + { + call.get_caller(user_id as u64) + .await + .unwrap() + .set_timeout(untill) + .await; + } + } + } + async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id()) + .await + .unwrap() + .has_admin() + { + call.remove_caller(user_id as u64).await; + } + } + } + + /// Send error response + async fn send_error_response(self: Arc, message_id: &u32, 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 is_open_guard = self.is_open.write().await; + if !*is_open_guard { + return; + } + *is_open_guard = false; + + let _ = self.sender.close(); + } + + #[allow(dead_code)] + /// Set interested users list + pub async fn set_interested_users(self: Arc, interested_ids: Vec) { + let mut interested_guard = self.interested_users.write().await; + *interested_guard = interested_ids; + } + #[allow(dead_code)] + pub async fn get_interested_users(self: Arc) -> Vec { + let interested_guard = self.interested_users.read().await; + interested_guard.clone() + } + + #[allow(dead_code)] + /// Check if interested in a user and send notification + pub async fn are_you_interested(self: Arc, user_id: i64) { + let interested_guard = self.clone().get_interested_users().await; + if interested_guard.contains(&user_id) { + let notification = CommunicationValue::new(CommunicationType::client_changed) + .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(¬ification).await; + } + } + + /// Handle connection close + pub async fn handle_close(&self) { + + // TODO delete temp user + } +} + +// Implement Clone to make it easier to work with Arc +impl Clone for AnonymousClientConnection { + fn clone(&self) -> Self { + Self { + sender: Arc::clone(&self.sender), + receiver: Arc::clone(&self.receiver), + user_id: self.user_id, + ping: Arc::clone(&self.ping), + interested_users: Arc::clone(&self.interested_users), + is_open: Arc::clone(&self.is_open), + user_name: Arc::clone(&self.user_name), + display_name: Arc::clone(&self.display_name), + avatar: Arc::clone(&self.avatar), + } + } +} diff --git a/src/anonymous_clients/anonymous_manager.rs b/src/anonymous_clients/anonymous_manager.rs new file mode 100644 index 0000000..edfefde --- /dev/null +++ b/src/anonymous_clients/anonymous_manager.rs @@ -0,0 +1,53 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::Rng; +use rand::seq::SliceRandom; +use std::sync::Arc; + +use crate::anonymous_clients::anonymous_client_connection::AnonymousClientConnection; + +static ANONYMOUS_USERS: Lazy>> = + Lazy::new(|| DashMap::new()); + +#[allow(dead_code)] +pub async fn add_anonymous_user(connection: Arc) { + ANONYMOUS_USERS.insert(connection.get_user_id(), connection); +} + +#[allow(dead_code)] +pub async fn remove_anonymous_user(user_id: u64) { + ANONYMOUS_USERS.remove(&user_id); +} + +pub async fn get_anonymous_user(user_id: u64) -> Option> { + ANONYMOUS_USERS.get(&user_id).map(|c| c.clone()) +} + +pub async fn get_anonymous_user_by_name( + username: String, +) -> Option> { + for user_conn in ANONYMOUS_USERS + .iter() + .map(|ref_multi| ref_multi.value().clone()) + { + if user_conn.get_user_name().await == username { + return Some(user_conn); + } + } + + return None; +} + +// TODO: implement check if taken +pub fn generate_username() -> String { + let adjectives = ["Swift", "Clever", "Brave", "Sneaky", "Fierce"]; + let nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Dragon"]; + + let mut rng = rand::thread_rng(); + let adj = adjectives.choose(&mut rng).unwrap(); + let noun = nouns.choose(&mut rng).unwrap(); + + let number: u16 = rng.gen_range(0..10000); + + format!("{}{}{}", adj, noun, number) +} diff --git a/src/anonymous_clients/mod.rs b/src/anonymous_clients/mod.rs new file mode 100644 index 0000000..3edd0b4 --- /dev/null +++ b/src/anonymous_clients/mod.rs @@ -0,0 +1,2 @@ +pub mod anonymous_client_connection; +pub mod anonymous_manager; diff --git a/src/calls/call_group.rs b/src/calls/call_group.rs new file mode 100644 index 0000000..91e85cf --- /dev/null +++ b/src/calls/call_group.rs @@ -0,0 +1,106 @@ +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; + +use std::{env, sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::{ + calls::{call_util, caller::Caller}, + omega::omega_connection::get_omega_connection, +}; + +pub struct CallGroup { + pub call_id: Uuid, + pub members: RwLock>>, + pub show: RwLock, + pub anonymous_joining: RwLock, + pub short_link: RwLock>, +} + +impl CallGroup { + pub fn new(call_id: Uuid, user: Arc) -> Self { + CallGroup { + call_id, + members: RwLock::new(vec![user]), + show: RwLock::new(true), + anonymous_joining: RwLock::new(false), + short_link: RwLock::new(None), + } + } + + pub async fn get_caller(&self, user_id: u64) -> Option> { + self.members + .read() + .await + .iter() + .find(|caller| caller.user_id == user_id) + .cloned() + } + + pub async fn is_anonymous(&self) -> bool { + *self.anonymous_joining.read().await + } + + pub async fn set_anonymous_joining(&self, enable: bool) { + *self.anonymous_joining.write().await = enable; + + let _ = call_util::set_room_metadata( + self.call_id, + format!("{{\"anonymous_joining\": \"{}\"}}", enable), + ) + .await; + + if self.short_link.read().await.is_none() { + let long_link = format!( + "https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}", + self.call_id, + env::var("ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0), + ); + let response_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::shorten_link) + .add_data(DataTypes::link, DataValue::Str(long_link)), + Some(Duration::from_secs(20)), + ) + .await; + if let Ok(response) = response_cv { + *self.short_link.write().await = Some( + response + .get_data(DataTypes::link) + .as_str() + .unwrap() + .to_string(), + ); + log::info!( + "Shortened link for call {} is {}", + self.call_id, + self.short_link.read().await.as_ref().unwrap() + ); + } + } + } + + pub async fn create_anonymous_token(&self, user_id: u64) -> Option { + if self.is_anonymous().await { + if let Ok(token) = call_util::create_token(user_id, self.call_id, false) { + return Some(token); + } + } + None + } + + pub async fn remove_caller(&self, user_id: u64) { + let _ = call_util::remove_participant(self.call_id, user_id).await; + self.members + .write() + .await + .retain(|caller| caller.user_id != user_id); + } + + pub async fn get_short_link(self: Arc) -> Option { + self.short_link.read().await.clone() + } +} diff --git a/src/calls/call_manager.rs b/src/calls/call_manager.rs new file mode 100644 index 0000000..a17f347 --- /dev/null +++ b/src/calls/call_manager.rs @@ -0,0 +1,97 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::sync::Arc; +use uuid::Uuid; + +use crate::calls::{call_group::CallGroup, caller::Caller}; + +pub static CALL_GROUPS: Lazy>> = Lazy::new(|| DashMap::new()); +#[allow(dead_code)] +pub async fn get_call_invites(user_id: u64) -> Vec> { + let mut callers = Vec::new(); + for (_, cg) in CALL_GROUPS.clone().into_iter() { + let members = cg.members.read().await; + for member in members.iter() { + if member.user_id == user_id { + callers.push(member.clone()); + } + } + } + callers +} + +pub async fn get_call(call_id: Uuid) -> Option> { + if let Some(b) = CALL_GROUPS.get(&call_id) { + Some(b.clone()) + } else { + None + } +} + +pub async fn get_call_groups(user_id: u64) -> Vec> { + let mut call_groups = Vec::new(); + for (_, cg) in CALL_GROUPS.clone().into_iter() { + let is_member = { + let members = cg.members.read().await; + members.iter().any(|m| m.user_id == user_id) + }; + + if is_member { + call_groups.push(cg.clone()); + } + } + call_groups +} + +pub async fn get_call_token(user_id: u64, call_id: Uuid) -> Option { + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let mut members = cg.members.write().await; + + if let Some(member) = members.iter().find(|m| m.user_id == user_id) { + return Some(member.create_token()); + } + + let new_caller = Arc::new(Caller::new(user_id, call_id, false)); + let token = new_caller.create_token(); + + members.push(new_caller); + + return Some(token); + } + + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let cg_clone = cg.clone(); + + let mut members = cg_clone.members.write().await; + if let Some(member) = members.iter().find(|m| m.user_id == user_id) { + return Some(member.create_token()); + } + let new_caller = Arc::new(Caller::new(user_id, call_id, false)); + let token = new_caller.create_token(); + members.push(new_caller); + return Some(token); + } + + let caller = Arc::new(Caller::new(user_id, call_id, true)); + let call_group = CallGroup::new(call_id, caller.clone()); + + CALL_GROUPS.insert(call_id, Arc::new(call_group)); + + Some(caller.create_token()) +} + +pub async fn add_invite(call_id: Uuid, inviter_id: u64, invitee_id: u64) -> bool { + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let mut members = cg.members.write().await; + + let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id); + + if is_inviter_member { + if !members.iter().any(|m| m.user_id == invitee_id) { + members.push(Arc::new(Caller::new(invitee_id, call_id, false))); + } + return true; + } + } + false +} diff --git a/src/calls/call_util.rs b/src/calls/call_util.rs new file mode 100644 index 0000000..57df005 --- /dev/null +++ b/src/calls/call_util.rs @@ -0,0 +1,151 @@ +use livekit_api::{ + access_token::{self}, + services::room::RoomClient, +}; +use livekit_protocol::Room; +use std::env; +use std::str::FromStr; +use std::time::Duration; +use uuid::Uuid; + +use crate::{calls::call_manager::CALL_GROUPS, log, log_err, util::logger::PrintType}; + +pub fn get_livekit() -> Result<(String, String, String), ()> { + let hostname = match env::var("LIVEKI_HOSTNAME") { + Ok(secret) => secret, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKI_HOSTNAME not set!"); + return Err(()); + } + }; + let api_key = match env::var("LIVEKIT_API_KEY") { + Ok(key) => key, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKIT_API_KEY not set!"); + return Err(()); + } + }; + let api_secret = match env::var("LIVEKIT_API_SECRET") { + Ok(secret) => secret, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKIT_API_SECRET not set!"); + return Err(()); + } + }; + Ok((hostname, api_key, api_secret)) +} + +pub fn create_token(user_id: u64, call_id: Uuid, has_admin: bool) -> Result { + let (_, api_key, api_secret) = get_livekit()?; + + let token = access_token::AccessToken::with_api_key(&api_key, &api_secret) + .with_identity(&user_id.to_string()) + .with_grants(access_token::VideoGrants { + room_join: true, + room_admin: has_admin, + room: call_id.to_string(), + ..Default::default() + }) + .with_metadata(&format!("{{\"isAdmin\":{}}}", has_admin)) + .to_jwt(); + if let Ok(token) = token { + Ok(token) + } else { + Err(()) + } +} +#[allow(dead_code)] +pub async fn get_room(call_id: Uuid) -> Result<(RoomClient, Room), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + let rooms = room_service.list_rooms(Vec::new()).await; + if let Ok(rooms) = rooms { + for room in rooms { + if room.name == call_id.to_string() { + return Ok((room_service, room)); + } + } + } + } + return Err(()); +} + +pub async fn remove_participant(call_id: Uuid, user_id: u64) -> Result<(), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + if let Ok(_) = room_service + .remove_participant(&call_id.to_string(), &user_id.to_string()) + .await + { + return Ok(()); + } + } + return Err(()); +} + +#[allow(dead_code)] +pub async fn get_room_metadata(call_id: Uuid) -> Result { + if let Ok((_, room)) = get_room(call_id).await { + Ok(room.metadata) + } else { + Err(()) + } +} + +pub async fn set_room_metadata(call_id: Uuid, metadata: String) -> Result<(), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + if let Ok(_) = room_service + .update_room_metadata(&call_id.to_string(), &metadata) + .await + { + return Ok(()); + } + } + return Err(()); +} + +pub fn garbage_collect_calls() { + tokio::spawn(async move { + loop { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + clean_calls(room_service).await; + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + }); +} +pub async fn clean_calls(room_service: RoomClient) { + let rooms = room_service.list_rooms(Vec::new()).await.unwrap(); + let mut call_ids: Vec = Vec::new(); + let mut no_users: Vec = Vec::new(); + for room in rooms { + if let Ok(id) = Uuid::from_str(&room.name) { + if room.num_participants == 0 { + no_users.push(id); + } + call_ids.push(id); + } + } + let size_pre = CALL_GROUPS.len(); + for (id, _) in CALL_GROUPS.clone().into_iter() { + if !call_ids.contains(&id) { + CALL_GROUPS.remove(&id); + } + } + for (_, cg) in CALL_GROUPS.clone().into_iter() { + *cg.show.write().await = !no_users.contains(&cg.call_id); + } + + let size_post = CALL_GROUPS.len(); + if size_pre - size_post != 0 { + log!( + 0, + PrintType::Call, + "Cleaned {} calls, {} remaining", + size_pre - size_post, + size_post + ); + } +} diff --git a/src/calls/caller.rs b/src/calls/caller.rs new file mode 100644 index 0000000..74e0c3b --- /dev/null +++ b/src/calls/caller.rs @@ -0,0 +1,49 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::calls::call_util; + +pub struct Caller { + pub user_id: u64, + pub call_id: Uuid, + pub has_admin: bool, + pub timeout: RwLock, +} + +impl Caller { + pub fn new(user_id: u64, call_id: Uuid, has_admin: bool) -> Self { + Caller { + user_id, + call_id, + has_admin, + timeout: RwLock::new(0), + } + } + #[allow(dead_code)] + pub fn set_admin(&mut self, has_admin: bool) { + self.has_admin = has_admin; + } + pub fn has_admin(&self) -> bool { + self.has_admin + } + #[allow(dead_code)] + pub async fn is_timeouted(&self) -> bool { + *self.timeout.read().await + > SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64 + } + pub async fn set_timeout(&self, timeout: i64) { + *self.timeout.write().await = timeout; + } + pub fn create_token(&self) -> String { + if let Ok(token) = call_util::create_token(self.user_id, self.call_id, self.has_admin()) { + token + } else { + String::new() + } + } +} diff --git a/src/calls/mod.rs b/src/calls/mod.rs new file mode 100644 index 0000000..8413354 --- /dev/null +++ b/src/calls/mod.rs @@ -0,0 +1,4 @@ +pub mod call_group; +pub mod call_manager; +pub mod call_util; +pub mod caller; diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src/sql/connection_status.rs b/src/data/user.rs similarity index 100% rename from src/sql/connection_status.rs rename to src/data/user.rs diff --git a/src/main.rs b/src/main.rs index 8143c9f..fa511ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,25 @@ -mod server; -mod sql; -mod transport; +mod anonymous_clients; +mod calls; +mod data; +mod omega; +mod rho; mod util; -use crate::sql::sql::initialize_db; -use crate::sql::sql::print_users; -use crate::transport::omikron_connection; -use crate::util::crypto_helper::load_public_key; -use crate::util::crypto_helper::load_secret_key; -use crate::util::logger::PrintType; -use crate::util::logger::startup; +use std::env; + use dotenv::dotenv; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; -use std::env; + +use crate::{ + calls::call_util::garbage_collect_calls, + omega::omega_connection::get_omega_connection, + rho::server::start, + util::{ + crypto_helper::{load_public_key, load_secret_key}, + logger::startup, + }, +}; static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); pub fn get_private_key() -> x448::Secret { @@ -32,34 +38,14 @@ async fn main() { } dotenv().ok(); startup(); - log_in!("Incoming messages"); - log_out!("Outgoing messages"); + get_omega_connection(); tokio::spawn(async move { - match omikron_connection::start(9187).await { - Err(e) => log_err!(0, PrintType::General, "{:?}", e), - _ => {} + if let Err(e) = start(959).await { + log_err!(0, util::logger::PrintType::General, "{}", e); } }); - - log!("Started"); - log!(" .env"); - if let Err(e) = initialize_db().await { - log!("[FATAL] Database initialization failed: {}", e); - log!( - "[FATAL] Please ensure the database is running and the .env file is configured correctly." - ); - return; - } else { - log!(" DB"); - } - if let Err(e) = print_users().await { - log!("[ERROR] Failed to print users: {}", e); - } else { - log!(" Users"); - } - - let _ = server::server::start(9188).await; + garbage_collect_calls(); tokio::signal::ctrl_c().await.unwrap(); } 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..ab84eb2 --- /dev/null +++ b/src/omega/omega_connection.rs @@ -0,0 +1,741 @@ +use crate::{ + data::user::UserStatus, + get_private_key, log, log_cv_in, log_cv_out, log_err, log_in, + rho::rho_manager::{self, RHO_CONNECTIONS, connection_count}, + util::{ + crypto_helper::{decrypt_b64, secret_key_to_base64}, + file_util::load_file_vec, + logger::PrintType, + }, +}; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::{collections::HashMap, env, sync::Arc, time::Duration}; +use tokio::{ + sync::{Mutex, RwLock, mpsc, watch}, + task::JoinHandle, + time::{Instant, sleep}, +}; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +// ============================================================================ +// Configuration +// ============================================================================ + +const OMEGA_HOST_DEFAULT: &str = "methanium.net"; +const OMEGA_PORT_DEFAULT: u16 = 9187; +const RECONNECT_DELAY: Duration = Duration::from_secs(5); +const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); +const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); +const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); +const TASK_MAX_AGE: Duration = Duration::from_secs(60); + +// ============================================================================ +// Waiting Task System +// ============================================================================ + +pub struct WaitingTask { + pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub inserted_at: Instant, +} + +pub static WAITING_TASKS: Lazy> = Lazy::new(DashMap::new); + +pub fn start_task_cleanup_loop() { + tokio::spawn(async { + loop { + sleep(TASK_CLEANUP_INTERVAL).await; + WAITING_TASKS.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE); + } + }); +} + +// ============================================================================ +// Connection State +// ============================================================================ + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ConnectionState { + Disconnected, + Connecting, + Connected { identified: bool }, +} + +#[allow(unused_variables)] +impl ConnectionState { + pub fn is_connected(&self) -> bool { + match self { + ConnectionState::Connected { identified } => true, + _ => false, + } + } + + #[allow(dead_code)] + pub fn is_identified(&self) -> bool { + match self { + ConnectionState::Connected { identified: true } => true, + _ => false, + } + } +} + +// ============================================================================ +// Omega Connection (Client-side with auto-reconnect) +// ============================================================================ +#[allow(dead_code)] +pub struct OmegaConnection { + state: Arc>, + sender: Arc>>>, + connection_loop_handle: Arc>>>, + host: String, + port: u16, + server_cert: Vec, + last_ping: Arc>, + heartbeat_handle: Arc>>>, + message_send_times: Arc>>, + pub connection_id: Uuid, + shutdown_tx: Arc>>>, + // Track if we should reconnect on close + reconnect_on_close: Arc>, +} + +impl OmegaConnection { + pub fn new() -> Self { + Self::with_host(OMEGA_HOST_DEFAULT, OMEGA_PORT_DEFAULT) + } + + pub fn with_host(host: &str, port: u16) -> Self { + // Load server certificate from default location + let server_cert = + load_file_vec("certs", "cert.pem").expect("Failed to load server certificate"); + + Self::with_host_and_cert(host, port, server_cert) + } + + // New constructor that accepts certificate directly + pub fn with_host_and_cert(host: &str, port: u16, server_cert: Vec) -> Self { + let (shutdown_tx, _) = watch::channel(false); + + OmegaConnection { + state: Arc::new(RwLock::new(ConnectionState::Disconnected)), + sender: Arc::new(RwLock::new(None)), + connection_loop_handle: Arc::new(Mutex::new(None)), + host: host.to_string(), + port, + server_cert, + last_ping: Arc::new(Mutex::new(-1)), + heartbeat_handle: Arc::new(Mutex::new(None)), + message_send_times: Arc::new(Mutex::new(HashMap::new())), + connection_id: Uuid::new_v4(), + shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), + reconnect_on_close: Arc::new(RwLock::new(true)), + } + } + + // ------------------------------------------------------------------------- + // Connection Management + // ------------------------------------------------------------------------- + + pub async fn start(self: Arc) { + // Cancel any existing connection loop + if let Some(handle) = self.connection_loop_handle.lock().await.take() { + handle.abort(); + } + + // Set reconnect flag + *self.reconnect_on_close.write().await = true; + + let self_clone = self.clone(); + let handle = tokio::spawn(async move { + self_clone.connection_loop().await; + }); + + *self.connection_loop_handle.lock().await = Some(handle); + } + + #[allow(dead_code)] + pub async fn stop(&self) { + // Disable reconnection + *self.reconnect_on_close.write().await = false; + + if let Some(tx) = self.shutdown_tx.lock().await.take() { + let _ = tx.send(true); + } + + if let Some(handle) = self.connection_loop_handle.lock().await.take() { + handle.abort(); + } + + if let Some(handle) = self.heartbeat_handle.lock().await.take() { + handle.abort(); + } + + // Close sender if connected + if let Some(sender) = self.sender.read().await.as_ref() { + sender.close(); + } + + *self.state.write().await = ConnectionState::Disconnected; + *self.sender.write().await = None; + } + + async fn connection_loop(self: Arc) { + let mut reconnect_delay = RECONNECT_DELAY; + let shutdown_rx = self.shutdown_tx.lock().await.as_ref().unwrap().subscribe(); + let mut shutdown_rx = shutdown_rx; + + loop { + if *shutdown_rx.borrow() { + log_in!(0, PrintType::Omega, "Connection loop shutting down"); + break; + } + + // Check if reconnection is enabled + if !*self.reconnect_on_close.read().await { + log_in!(0, PrintType::Omega, "Reconnection disabled, exiting loop"); + break; + } + + match self.clone().connect_once().await { + Ok(()) => { + // Connection closed gracefully, check if we should reconnect + if *self.reconnect_on_close.read().await { + log_err!( + 0, + PrintType::Omega, + "Connection lost, reconnecting in {:?}...", + reconnect_delay + ); + } else { + log_in!(0, PrintType::Omega, "Connection closed, not reconnecting"); + break; + } + } + Err(e) => { + log_err!( + 0, + PrintType::Omega, + "Connection failed: {}, retrying in {:?}...", + e, + reconnect_delay + ); + } + } + + tokio::select! { + _ = sleep(reconnect_delay) => {} + _ = shutdown_rx.changed() => { + if *shutdown_rx.borrow() { + break; + } + } + } + + reconnect_delay = std::cmp::min(reconnect_delay * 2, MAX_RECONNECT_DELAY); + } + } + + async fn connect_once(self: Arc) -> Result<(), String> { + *self.state.write().await = ConnectionState::Connecting; + + let addr_str = format!("https://{}:{}", self.host, self.port); + + let (sender, mut receiver) = ttp_native::client::connect(&addr_str, None) + .await + .map_err(|e| format!("Connection failed: {}", e))?; + + log_in!( + 0, + PrintType::Omega, + "QUIC connection established to {}", + addr_str + ); + + // Store sender + let sender_arc = Arc::new(sender); + *self.sender.write().await = Some(sender_arc.clone()); + *self.state.write().await = ConnectionState::Connected { identified: false }; + + // Get handle for close monitoring + let sender_handle = sender_arc.handle().clone(); + + // Start read loop + let read_self = self.clone(); + let read_handle = tokio::spawn(async move { + read_self.read_loop(&mut receiver, sender_handle).await; + }); + + // Send identification + self.send_identification().await; + + // Start heartbeat + let heartbeat_self = self.clone(); + let heartbeat_handle = tokio::spawn(async move { + heartbeat_self.heartbeat_loop().await; + }); + *self.heartbeat_handle.lock().await = Some(heartbeat_handle); + + // Wait for read loop to complete (connection closed) + let result = read_handle.await; + + // Cleanup + *self.sender.write().await = None; + *self.state.write().await = ConnectionState::Disconnected; + + if let Some(handle) = self.heartbeat_handle.lock().await.take() { + handle.abort(); + } + + match result { + Ok(()) => { + // Check if we should reconnect + if *self.reconnect_on_close.read().await { + Err("Connection closed, will reconnect".to_string()) + } else { + Ok(()) + } + } + Err(e) => Err(format!("Read loop error: {}", e)), + } + } + + // ------------------------------------------------------------------------- + // Identification Handshake + // ------------------------------------------------------------------------- + + async fn send_identification(&self) { + let id = rand_u32(); + + let omikron_id = env::var("ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0); + + let identify_msg = CommunicationValue::new(CommunicationType::identification) + .with_id(id) + .add_data(DataTypes::omikron_id, DataValue::Number(omikron_id)); + + WAITING_TASKS.insert( + id, + WaitingTask { + task: Box::new(|selfc, cv| { + if cv.is_type(CommunicationType::error_not_found) { + log_err!( + 0, + PrintType::Omega, + "Identification failed: Omikron ID not found" + ); + return false; + } + if !cv.is_type(CommunicationType::challenge) { + return false; + } + + tokio::spawn(async move { + if let Err(e) = selfc.handle_challenge(cv).await { + log_err!(0, PrintType::Omega, "Challenge handling failed: {}", e); + } + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(&identify_msg).await; + } + + async fn handle_challenge(&self, cv: CommunicationValue) -> Result<(), String> { + let challenge = cv + .get_data(DataTypes::challenge) + .as_str() + .ok_or("Challenge not found")?; + + let server_pub_key = cv + .get_data(DataTypes::public_key) + .as_str() + .ok_or("Public key not found")?; + + let decrypted_challenge = decrypt_b64( + &secret_key_to_base64(&get_private_key()), + server_pub_key, + challenge, + ) + .map_err(|e| format!("Decryption failed: {:?}", e))?; + + let response_msg = CommunicationValue::new(CommunicationType::challenge_response) + .with_id(cv.get_id()) + .add_data(DataTypes::challenge, DataValue::Str(decrypted_challenge)); + + let response_id = response_msg.get_id(); + + WAITING_TASKS.insert( + response_id, + WaitingTask { + task: Box::new(|selfc, final_cv| { + if !final_cv.is_type(CommunicationType::identification_response) { + log_err!(0, PrintType::Omega, "Expected identification_response"); + return false; + } + + let accepted = final_cv + .get_data(DataTypes::accepted) + .as_bool() + .unwrap_or(false); + + if !accepted { + log_err!(0, PrintType::Omega, "Omega did not accept identification"); + return false; + } + + tokio::spawn(async move { + let mut state = selfc.state.write().await; + if let ConnectionState::Connected { identified: _ } = *state { + *state = ConnectionState::Connected { identified: true }; + } + drop(state); + + selfc.sync_client_iota_status().await; + }); + + log!(0, PrintType::Omega, "Successfully identified with Omega"); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(&response_msg).await; + Ok(()) + } + + async fn sync_client_iota_status(self: Arc) { + let mut connected_iota_ids: Vec = Vec::new(); + let mut connected_user_ids: Vec = Vec::new(); + + let rho_connections_reader = RHO_CONNECTIONS.read().await; + + for iota_id in rho_connections_reader.keys() { + connected_iota_ids.push(DataValue::Number(*iota_id)); + } + + for rho in rho_connections_reader.values() { + for client_conn in rho.get_client_connections().await { + connected_user_ids.push(DataValue::Number(client_conn.get_user_id().await as i64)); + } + } + + drop(rho_connections_reader); + + let sync_msg = CommunicationValue::new(CommunicationType::sync_client_iota_status) + .add_data(DataTypes::iota_ids, DataValue::Array(connected_iota_ids)) + .add_data(DataTypes::user_ids, DataValue::Array(connected_user_ids)) + .add_data( + DataTypes::rho_connections, + DataValue::Number(connection_count().await as i64), + ); + + self.send_message(&sync_msg).await; + } + + // ------------------------------------------------------------------------- + // Read Loop & Heartbeat + // ------------------------------------------------------------------------- + + async fn read_loop( + self: Arc, + receiver: &mut Receiver, + sender_handle: Arc, + ) { + // Monitor both receiver and sender handle for close + let mut close_rx = sender_handle.subscribe_close(); + + loop { + tokio::select! { + result = receiver.receive() => { + match result { + Ok(cv) => { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_in!(PrintType::Omega, &cv); + } + + if cv.is_type(CommunicationType::pong) || cv.is_type(CommunicationType::ping) { + self.handle_pong(&cv).await; + continue; + } + + let msg_id = cv.get_id(); + if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { + if (task.task)(self.clone(), cv.clone()) { + continue; + } + } + + if cv.is_type(CommunicationType::iota_user_data) { + if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { + let mut user_ids: Vec = Vec::new(); + for value in users { + if let DataValue::Number(user_id) = value { + user_ids.push(*user_id as u64); + } + } + let connections = crate::rho::rho_manager::RHO_CONNECTIONS.read().await; + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Some(rho) = connections.get(&iota_id) { + rho.get_iota_connection().set_user_ids(user_ids).await; + } + } else { + for rho in connections.values() { + rho.get_iota_connection().set_user_ids(user_ids.clone()).await; + } + } + } + } + } + Err(e) => { + log_err!(0, PrintType::Omega, "Receive error: {}", e); + break; + } + } + } + _ = close_rx.changed() => { + // Connection was closed by either side + if let Some(reason) = close_rx.borrow().clone() { + log_err!(0, PrintType::Omega, "Connection closed: {:?}", reason); + } else { + log_in!(0, PrintType::Omega, "Connection closed cleanly"); + } + break; + } + } + } + } + + async fn heartbeat_loop(self: Arc) { + loop { + sleep(HEARTBEAT_INTERVAL).await; + + // Check if still connected + if !self.state.read().await.is_connected() { + break; + } + + // Check if sender is closed + if let Some(sender) = self.sender.read().await.as_ref() { + if sender.is_closed() { + log_err!(0, PrintType::Omega, "Sender closed, stopping heartbeat"); + break; + } + } else { + break; + } + + self.send_ping().await; + } + } + + async fn send_ping(&self) { + let ping = CommunicationValue::new(CommunicationType::ping).add_data( + DataTypes::send_time, + DataValue::Number( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64, + ), + ); + self.send_message(&ping).await; + } + + async fn handle_pong(&self, cv: &CommunicationValue) { + let timestamp = cv + .get_data(DataTypes::send_time) + .as_number() + .unwrap_or_else(|| { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64 + }); + + *self.last_ping.lock().await = timestamp; + } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + + pub async fn send_message(&self, cv: &CommunicationValue) { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Omega, &cv); + } + + let sender_guard = self.sender.read().await; + if let Some(sender) = sender_guard.as_ref() { + // Check if closed before sending + if sender.is_closed() { + log_err!(0, PrintType::Omega, "Cannot send: connection closed"); + drop(sender_guard); + // Trigger reconnection by closing the connection state + if let Some(sender) = self.sender.write().await.take() { + sender.close(); + } + return; + } + + let sender_clone = Arc::clone(sender); + drop(sender_guard); + + if let Err(e) = sender_clone.send(cv).await { + log_err!(0, PrintType::Omega, "Send failed: {}", e); + } + } else { + log_err!(0, PrintType::Omega, "Cannot send: not connected"); + } + } + + pub async fn await_connection(&self, timeout_duration: Option) -> Result<(), String> { + if self.state.read().await.is_connected() { + return Ok(()); + } + + let timeout = timeout_duration.unwrap_or(CONNECTION_TIMEOUT); + let start = Instant::now(); + + loop { + if self.state.read().await.is_connected() { + return Ok(()); + } + + if start.elapsed() >= timeout { + return Err(format!( + "Connection not established within {} seconds", + timeout.as_secs() + )); + } + + sleep(Duration::from_millis(100)).await; + } + } + + pub async fn await_response( + &self, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + self.await_connection(timeout_duration).await?; + + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + WAITING_TASKS.insert( + msg_id, + WaitingTask { + task: Box::new(move |_, response_cv| { + let inner_tx = tx.clone(); + tokio::spawn(async move { + let _ = inner_tx.send(response_cv).await; + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(_) => Err("Channel closed".to_string()), + Err(_) => { + WAITING_TASKS.remove(&msg_id); + Err("Request timed out".to_string()) + } + } + } + + #[allow(dead_code)] + pub async fn is_connected(&self) -> bool { + self.state.read().await.is_connected() + } + #[allow(dead_code)] + pub async fn is_identified(&self) -> bool { + self.state.read().await.is_identified() + } + + #[allow(dead_code)] + pub async fn close_iota(iota_id: i64) { + let cv = CommunicationValue::new(CommunicationType::iota_disconnected) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + OMEGA_CONNECTION.send_message(&cv).await; + } + + pub async fn client_changed(_iota_id: i64, user_id: i64, state: UserStatus) { + let msg_type = match state { + UserStatus::iota_offline => CommunicationType::user_disconnected, + UserStatus::user_offline => CommunicationType::user_disconnected, + _ => CommunicationType::user_connected, + }; + + let cv = CommunicationValue::new(msg_type) + .add_data(DataTypes::user_id, DataValue::Number(user_id)); + OMEGA_CONNECTION.send_message(&cv).await; + } + + pub async fn user_states(user_id: i64, user_ids: Vec) { + let user_ids = user_ids.iter().map(|v| DataValue::Number(*v)).collect(); + + let cv = CommunicationValue::new(CommunicationType::get_states) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + let msg_id = cv.get_id(); + + WAITING_TASKS.insert( + msg_id, + WaitingTask { + task: Box::new( + move |_: Arc, response: CommunicationValue| { + tokio::spawn(async move { + 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 + }, + ), + inserted_at: Instant::now(), + }, + ); + + OMEGA_CONNECTION.send_message(&cv).await; + } +} + +// ============================================================================ +// Global Instance +// ============================================================================ + +static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { + let conn = Arc::new(OmegaConnection::new()); + + // Start the connection manager immediately + let conn_clone = conn.clone(); + tokio::spawn(async move { + conn_clone.start().await; + }); + + start_task_cleanup_loop(); + + conn +}); + +pub fn get_omega_connection() -> Arc { + OMEGA_CONNECTION.clone() +} diff --git a/src/omega/ping_pong_task.rs b/src/omega/ping_pong_task.rs new file mode 100644 index 0000000..c7fc0b9 --- /dev/null +++ b/src/omega/ping_pong_task.rs @@ -0,0 +1,41 @@ +use std::time::Duration; +use tokio::time::Instant; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; + +use crate::omega::omega_connection::OmegaConnection; + +const PING_TIMEOUT: Duration = Duration::from_secs(30); + +impl OmegaConnection { + pub async fn send_ping(&self) { + let id = rand_u32(); + let send_time = Instant::now(); + + let mut message_send_times = self.message_send_times.lock().await; + message_send_times.retain(|_uuid, time| time.elapsed() < PING_TIMEOUT); + message_send_times.insert(id as i64, send_time); + + self.send_ping_message(id).await; + } + + pub async fn send_ping_message(&self, id: u32) { + let ping_message = CommunicationValue::new(CommunicationType::ping) + .with_id(id) + .add_data( + DataTypes::last_ping, + DataValue::Number(self.last_ping.lock().await.unwrap()), + ); + + self.send_message(&ping_message).await; + } + + /// Handles incoming pong and calculates latency + pub async fn handle_pong(&self, cv: &CommunicationValue, _log: bool) { + let id = cv.get_id(); + let mut message_send_times = self.message_send_times.lock().await; + if let Some(send_time) = message_send_times.remove(&(id as i64)) { + let ping = Instant::now().duration_since(send_time).as_millis() as i64; + *self.last_ping.lock().await = ping; + } + } +} diff --git a/src/rho/client_connection.rs b/src/rho/client_connection.rs new file mode 100644 index 0000000..ebaabf2 --- /dev/null +++ b/src/rho/client_connection.rs @@ -0,0 +1,583 @@ +use crate::anonymous_clients::anonymous_manager; +use crate::calls::{call_manager, call_util}; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::rho::{rho_connection::RhoConnection, rho_manager}; +use crate::util::logger::PrintType; +use crate::{data::user::UserStatus, omega::omega_connection::OmegaConnection}; +use crate::{log_cv_in, log_cv_out, log_err, log_in, log_out}; +use std::str::FromStr; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +pub struct ClientConnection { + pub user_id: u64, + + pub sender: Arc, + pub receiver: Arc, + pub ping: Arc>, + pub_key: Arc>>>, + pub rho_connection: Arc>>>, + pub interested_users: Arc>>, + is_open: Arc>, +} + +impl ClientConnection { + pub async fn from_general(general: Arc, user_id: u64) -> Arc { + Arc::new(Self { + ping: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: general.rho_connection.clone(), + interested_users: Arc::new(RwLock::new(Vec::new())), + is_open: Arc::new(RwLock::new(true)), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + user_id: user_id, + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + while let Ok(cv) = self_clone.receiver.receive().await { + self_clone.clone().handle_message(cv).await; + } + self_clone.handle_close().await; + }); + + let self_clone2 = self.clone(); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(50)).await; + if self_clone2.get_rho_connection().await.is_none() { + self_clone2 + .send_error_response(0, CommunicationType::error) + .await; + } + }); + } + + /// Get the user ID + pub async fn get_user_id(&self) -> u64 { + self.user_id + } + + /// Get current ping + pub async fn get_ping(&self) -> i64 { + *self.ping.read().await + } + + /// Get RhoConnection if available + pub async fn get_rho_connection(&self) -> Option> { + self.rho_connection.read().await.clone() + } + + /// Send a CommunicationValue to the client + pub async fn send_message(self: Arc, cv: &CommunicationValue) { + if !*self.is_open.read().await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Attempted to send message to a closed connection." + ); + return; + } + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Client, &cv); + } + let _ = self.sender.send(&cv).await; + } + + /// Handle incoming message from client + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + tokio::spawn(async move { + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_cv_in!(PrintType::Client, cv); + + // 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::call_token) { + self.handle_get_call(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_disconnect_user) { + self.handle_call_disconnect_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_timeout_user) { + self.handle_call_timeout_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_set_anonymous_joining) { + self.handle_call_set_anonymous_joining(cv).await; + return; + } + if cv.is_type(CommunicationType::get_user_data) { + if let Some(anonymous) = { + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + anonymous_manager::get_anonymous_user(user_id as u64).await + } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { + anonymous_manager::get_anonymous_user_by_name(username.to_string()).await + } else { + None + } + } { + let response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data( + DataTypes::username, + DataValue::Str(anonymous.get_user_name().await), + ) + .add_data( + DataTypes::user_id, + DataValue::Number(anonymous.get_user_id() as i64), + ) + .add_data( + DataTypes::display, + DataValue::Str(anonymous.get_display_name().await), + ) + .add_data( + DataTypes::avatar, + DataValue::Str(anonymous.get_avatar().await), + ) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(&response).await; + + return; + } + } + + if cv.is_type(CommunicationType::change_user_data) + || cv.is_type(CommunicationType::read_notification) + || cv.is_type(CommunicationType::get_notifications) + || cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::delete_user) + { + let sender = self.get_user_id().await; + self.handle_omega_forward(cv.with_sender(sender as u64)) + .await; + return; + } + // Forward other messages to Iota + self.forward_to_iota(cv).await; + }); + } + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let client_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + client_for_closure.send_message(&response_cv).await; + } + }); + } + + /// Handle ping message + async fn handle_ping(self: Arc, cv: CommunicationValue) { + // Update our ping if provided + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + let current = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + let mut ping_guard = self.ping.write().await; + *ping_guard = current as i64 - last_ping; + } + + // 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(DataTypes::ping_iota, DataValue::Number(iota_ping)); + + self.send_message(&response).await; + } + + /// Handle client status change + async fn handle_client_changed(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id().await; + if let DataValue::Str(_status_str) = cv.get_data(DataTypes::user_state) { + let user_status = UserStatus::user_online; + if let Some(rho_conn) = self.get_rho_connection().await { + OmegaConnection::client_changed( + rho_conn.get_iota_id().await as i64, + user_id as i64, + user_status, + ) + .await; + } + } + } + + /// Handle call invite + async fn handle_call_invite(self: Arc, cv: CommunicationValue) { + let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); + if receiver_id == 0 { + self.send_error_response(cv.get_id(), CommunicationType::error_no_user_id) + .await; + return; + } + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + }, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error_no_call_id) + .await; + return; + } + }; + + let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; + if !invited { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + + // Find target RhoConnection + let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { + Some(rho) => rho, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Get sender user ID + let sender_id = self.get_user_id().await; + + // Create and send call distribution message + let forward = CommunicationValue::new(CommunicationType::call_invite) + .with_receiver(receiver_id as u64) + .with_sender(sender_id as u64) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data( + DataTypes::receiver_id, + DataValue::Str(receiver_id.to_string()), + ) + .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); + + target_rho.message_to_client(forward).await; + + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle get call request + async fn handle_get_call(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id().await; + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + if let Some(token) = call_manager::get_call_token(user_id, call_id).await { + let response = CommunicationValue::new(CommunicationType::call_token) + .with_id(cv.get_id()) + .with_receiver(user_id as u64) + .add_data(DataTypes::call_token, DataValue::Str(token)); + self.send_message(&response).await; + } else { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + } + async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + let _ = call_util::remove_participant(call_id, user_id as u64).await; + call.get_caller(user_id as u64) + .await + .unwrap() + .set_timeout(untill) + .await; + } + } + } + async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + call.remove_caller(user_id as u64).await; + } + } + } + async fn handle_call_set_anonymous_joining(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let enable = cv.get_data(DataTypes::enabled).as_bool().unwrap_or(true); + + let call = call_manager::get_call(call_id).await; + + let mut short_link = None; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + call.set_anonymous_joining(enable).await; + } + short_link = call.get_short_link().await; + } + let mut response_cv = + CommunicationValue::new(CommunicationType::call_set_anonymous_joining) + .with_id(cv.get_id()) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data(DataTypes::enabled, DataValue::Bool(enable)); + if let Some(short_link) = short_link { + response_cv = response_cv.add_data(DataTypes::link, DataValue::Str(short_link)); + } + self.send_message(&response_cv).await; + } + + /// Forward message to Iota + async fn forward_to_iota(self: Arc, cv: CommunicationValue) { + let sender_user_id = self.get_user_id().await; + let msg_id = cv.get_id(); + let msg_type = cv.get_type(); + + log_in!( + sender_user_id as i64, + PrintType::Client, + "Forwarding client->iota: sender={} type={:?} id={} receiver={}", + sender_user_id, + msg_type, + msg_id, + cv.get_receiver() + ); + + if cv.is_type(CommunicationType::add_conversation) + && cv + .get_data(DataTypes::chat_partner_id) + .as_number() + .is_none() + { + let chat_partner_name = cv + .get_data(DataTypes::chat_partner_name) + .as_str() + .unwrap_or("") + .to_string(); + + if anonymous_manager::get_anonymous_user_by_name(chat_partner_name.to_string()) + .await + .is_some() + { + self.send_error_response(cv.get_id(), CommunicationType::error_anonymous) + .await; + return; + } + + let load_uuid_response = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.clone().get_id()) + .add_data( + DataTypes::username, + DataValue::Str(chat_partner_name.clone()), + ), + Some(Duration::from_secs(20)), + ) + .await; + let chat_partner_id = { + if let Ok(load_uuid_response) = load_uuid_response { + load_uuid_response.get_data(DataTypes::user_id).clone() + } else { + DataValue::Null + } + }; + + if let Some(rho_conn) = self.get_rho_connection().await { + let iota_id = rho_conn.get_iota_id().await; + log_in!( + sender_user_id as i64, + PrintType::Client, + "Resolved rho for add_conversation: sender={} -> iota_id={} id={}", + sender_user_id, + iota_id, + msg_id + ); + + let updated_cv = cv + .with_sender(sender_user_id as u64) + .add_data(DataTypes::chat_partner_id, chat_partner_id); + rho_conn.message_to_iota(updated_cv).await; + } else { + log_err!( + sender_user_id as i64, + PrintType::Client, + "No rho/iota mapping found for add_conversation sender={} type={:?} id={}", + sender_user_id, + msg_type, + msg_id + ); + } + return; + } + + if let Some(rho_conn) = self.get_rho_connection().await { + let iota_id = rho_conn.get_iota_id().await; + log_in!( + sender_user_id as i64, + PrintType::Client, + "Resolved rho for forward: sender={} -> iota_id={} type={:?} id={}", + sender_user_id, + iota_id, + msg_type, + msg_id + ); + + let updated_cv = cv.with_sender(sender_user_id as u64); + rho_conn.message_to_iota(updated_cv).await; + } else { + log_err!( + sender_user_id as i64, + PrintType::Client, + "No rho/iota mapping found for sender={} type={:?} id={}", + sender_user_id, + msg_type, + msg_id + ); + self.send_error_response(msg_id, CommunicationType::error) + .await; + } + } + + /// Send error response + async fn send_error_response(self: Arc, message_id: u32, 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 is_open_guard = self.is_open.write().await; + if !*is_open_guard { + return; + } + *is_open_guard = false; + + let _ = self.sender.close(); + } + + /// Set interested users list + pub async fn set_interested_users(self: Arc, interested_ids: Vec) { + let mut interested_guard = self.interested_users.write().await; + *interested_guard = interested_ids; + } + #[allow(dead_code)] + pub async fn get_interested_users(self: Arc) -> Vec { + let interested_guard = self.interested_users.read().await; + interested_guard.clone() + } + + /// Check if interested in a user and send notification + #[allow(dead_code)] + pub async fn are_you_interested(self: Arc, user_id: i64) { + let interested_guard = self.clone().get_interested_users().await; + if interested_guard.contains(&user_id) { + let notification = CommunicationValue::new(CommunicationType::client_changed) + .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(¬ification).await; + } + } + + /// Handle connection close + pub async fn handle_close(&self) { + let user_id = self.get_user_id().await; + if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id as i64).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 { + sender: Arc::clone(&self.sender), + receiver: Arc::clone(&self.receiver), + user_id: self.user_id, + ping: Arc::clone(&self.ping), + pub_key: Arc::clone(&self.pub_key), + rho_connection: Arc::clone(&self.rho_connection), + interested_users: Arc::clone(&self.interested_users), + is_open: Arc::clone(&self.is_open), + } + } +} diff --git a/src/rho/connection.rs b/src/rho/connection.rs new file mode 100755 index 0000000..ca746f0 --- /dev/null +++ b/src/rho/connection.rs @@ -0,0 +1,358 @@ +use rand::{Rng, distributions::Alphanumeric}; +use std::{sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; + +use crate::{ + anonymous_clients::anonymous_client_connection::AnonymousClientConnection, + get_private_key, get_public_key, log_cv_in, log_cv_out, log_err, log_in, log_out, + omega::omega_connection::get_omega_connection, + rho::{ + client_connection::ClientConnection, iota_connection::IotaConnection, + rho_connection::RhoConnection, rho_manager, + }, + util::{ + crypto_helper::{load_public_key, public_key_to_base64}, + crypto_util::{DataFormat, SecurePayload}, + logger::PrintType, + }, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(dead_code)] +pub enum ConnectionKind { + Client, + Iota, + AnonymousClient, + Phi, +} + +pub struct GeneralConnection { + pub sender: Arc, + pub receiver: Arc, + + identified: Arc>, + challenged: Arc>, + challenge: Arc>, + + connection_kind: Arc>>, + pub rho_connection: Arc>>>, + id: Arc>, + + pub_key: Arc>>>, +} +impl GeneralConnection { + pub fn new(sender: Sender, receiver: Receiver) -> Arc { + Arc::new(Self { + sender: Arc::new(sender), + receiver: Arc::new(receiver), + identified: Arc::new(RwLock::new(false)), + challenged: Arc::new(RwLock::new(false)), + challenge: Arc::new(RwLock::new(String::new())), + connection_kind: Arc::new(RwLock::new(None)), + rho_connection: Arc::new(RwLock::new(None)), + id: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + }) + } +} +impl GeneralConnection { + pub async fn handle(self: Arc) { + log_in!(0, PrintType::General, "General connection handler started"); + + loop { + let cv = match self.receiver.receive().await { + Ok(v) => v, + Err(_) => { + break; + } + }; + + log_cv_in!(cv); + + if !*self.identified.read().await { + self.handle_identification(cv).await; + continue; + } + + if !*self.challenged.read().await { + self.handle_challenge_response(cv).await; + } + + if *self.challenged.read().await { + let self_clone = self.clone(); + tokio::spawn(async move { + self_clone.migrate().await; + }); + break; + } + } + + log_out!(0, PrintType::General, "General connection handler stopped"); + } + async fn handle_identification(self: &Arc, cv: CommunicationValue) { + if !cv.is_type(CommunicationType::identification) { + return; + } + + if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { + *self.id.write().await = *iota_id as u64; + *self.connection_kind.write().await = Some(ConnectionKind::Iota); + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .add_data(DataTypes::iota_id, DataValue::Number(*iota_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + let response_cv = match response_cv { + Ok(r) => r, + Err(_) => { + return; + } + }; + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .as_str() + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + *self.identified.write().await = true; + + let encrypted_challenge = + SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(public_key_to_base64(&get_public_key())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); + + log_cv_out!(response); + let _ = self.sender.send(&response).await; + } else if let DataValue::Number(user_id) = cv.get_data(DataTypes::user_id) { + *self.id.write().await = *user_id as u64; + *self.connection_kind.write().await = Some(ConnectionKind::Client); + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) + .add_data(DataTypes::user_id, DataValue::Number(*user_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + let response_cv = match response_cv { + Ok(r) => r, + Err(_) => { + return; + } + }; + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .as_str() + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + *self.identified.write().await = true; + + let encrypted_challenge = + SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(public_key_to_base64(&get_public_key())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); + + log_cv_out!(response); + let _ = self.sender.send(&response).await; + } + } + async fn handle_challenge_response(self: &Arc, cv: CommunicationValue) { + let id = *self.id.read().await as i64; + + if !cv.is_type(CommunicationType::challenge_response) { + return; + } + + if let DataValue::Str(response) = cv.get_data(DataTypes::challenge) { + let expected = self.challenge.read().await.clone(); + + if *response == expected { + *self.challenged.write().await = true; + + let response = CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, DataValue::Bool(true)); + + log_cv_out!(response); + if let Err(_) = self.sender.send(&response).await { + return; + } + } else { + log_err!( + id, + PrintType::Iota, + "Challenge response mismatch expected={} actual={}", + expected, + response + ); + } + } else { + log_err!( + id, + PrintType::Iota, + "Challenge response missing challenge payload" + ); + } + } + + async fn migrate(self: &Arc) -> bool { + let kind = match *self.connection_kind.read().await { + Some(kind) => kind, + None => { + return false; + } + }; + let id = *self.id.read().await; + + match kind { + ConnectionKind::Client => { + let notify = CommunicationValue::new(CommunicationType::user_connected) + .add_data(DataTypes::user_id, DataValue::Number(id as i64)); + get_omega_connection().send_message(¬ify).await; + + let user_id = id as i64; + + let client = ClientConnection::from_general(self.clone(), id).await; + + let mut rho = rho_manager::get_rho_con_for_user(user_id).await; + + if rho.is_none() { + let get_user_msg = CommunicationValue::new(CommunicationType::get_user_data) + .add_data(DataTypes::user_id, DataValue::Number(user_id)); + + if let Ok(user_data_cv) = get_omega_connection() + .await_response(&get_user_msg, Some(Duration::from_secs(20))) + .await + { + if let DataValue::Number(iota_id) = + user_data_cv.get_data(DataTypes::iota_id) + { + if let Some(bound_rho) = + rho_manager::bind_user_to_iota(user_id, *iota_id).await + { + bound_rho.bind_user_id(user_id).await; + rho = Some(bound_rho); + } + } + } + } + + *self.rho_connection.write().await = rho.clone(); + + if let Some(rho_conn) = rho { + rho_conn.bind_user_id(user_id).await; + rho_conn.add_client_connection(client.clone()).await; + } else { + log_err!( + user_id, + PrintType::Client, + "No RhoConnection found for user {}, client not attached to iota", + id + ); + } + + client.start(); + } + ConnectionKind::Iota => { + let notify = CommunicationValue::new(CommunicationType::iota_connected) + .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); + get_omega_connection().send_message(¬ify).await; + + let iota = IotaConnection::from_general(self.clone(), id).await; + + let rho = Arc::new(RhoConnection::new(iota.clone(), Vec::new()).await); + + iota.set_rho_connection(rho.clone()).await; + + rho_manager::add_rho(rho).await; + + let get_iota_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); + + if let Ok(iota_data_cv) = get_omega_connection() + .await_response(&get_iota_msg, Some(Duration::from_secs(20))) + .await + { + if let DataValue::Array(users) = iota_data_cv.get_data(DataTypes::user_ids) { + let mut user_ids: Vec = Vec::new(); + for value in users { + if let DataValue::Number(user_id) = value { + user_ids.push(*user_id as u64); + } + } + iota.set_user_ids(user_ids).await; + } + } + + iota.clone().start(); + } + ConnectionKind::AnonymousClient => { + let client = AnonymousClientConnection::from_general(self.clone(), id).await; + client.start(); + } + ConnectionKind::Phi => { + let phi = ClientConnection::from_general(self.clone(), id).await; + phi.start(); + } + } + true + } +} diff --git a/src/rho/iota_connection.rs b/src/rho/iota_connection.rs new file mode 100755 index 0000000..41336ea --- /dev/null +++ b/src/rho/iota_connection.rs @@ -0,0 +1,461 @@ +use crate::calls::call_group::CallGroup; +use crate::calls::call_manager; +use crate::log_cv_in; +use crate::log_cv_out; +use crate::log_err; +use crate::log_in; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::util::logger::PrintType; +use dashmap::DashMap; +use std::collections::BTreeMap; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use tokio::sync::mpsc; +use ttp_core::CommunicationType; +use ttp_core::CommunicationValue; +use ttp_core::DataTypes; +use ttp_core::DataValue; +use ttp_native::Receiver; +use ttp_native::Sender; +use x448::PublicKey; + +use super::{rho_connection::RhoConnection, rho_manager}; +use crate::omega::omega_connection::OmegaConnection; + +#[allow(dead_code)] +pub struct IotaConnection { + pub iota_id: u64, + pub sender: Arc, + pub receiver: Arc, + pub user_ids: Arc>>, + pub ping: Arc>, + pub_key: Arc>>>, + pub waiting_tasks: + DashMap, CommunicationValue) -> bool + Send + Sync>>, + pub rho_connection: Arc>>>, +} + +impl IotaConnection { + pub async fn from_general(general: Arc, iota_id: u64) -> Arc { + Arc::new(Self { + ping: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: general.rho_connection.clone(), + user_ids: Arc::new(RwLock::new(Vec::new())), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + iota_id: iota_id, + waiting_tasks: DashMap::new(), + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + loop { + match self_clone.receiver.receive().await { + Ok(cv) => { + self_clone.clone().handle_message(cv).await; + } + Err(_) => { + break; + } + } + } + self_clone.handle_close().await; + }); + } + + /// Get the Iota ID + pub async fn get_iota_id(&self) -> u64 { + self.iota_id + } + + #[allow(dead_code)] + pub async fn get_public_key(&self) -> Option { + if let Some(public_key) = self.pub_key.read().await.clone() { + PublicKey::from_bytes(&public_key) + } else { + None + } + } + + /// Get the user IDs + pub async fn get_user_ids(&self) -> Vec { + self.user_ids.read().await.clone() + } + + /// Replace all users linked to this iota and synchronize the attached rho mapping. + pub async fn set_user_ids(&self, user_ids: Vec) { + { + let mut guard = self.user_ids.write().await; + *guard = user_ids.clone(); + } + + if let Some(rho_conn) = self.get_rho_connection().await { + let user_ids_i64: Vec = user_ids.into_iter().map(|u| u as i64).collect(); + rho_conn.set_user_ids(user_ids_i64).await; + } + } + + pub async fn add_user_id(&self, user_id: u64) { + let mut should_sync = false; + { + let mut guard = self.user_ids.write().await; + if !guard.contains(&user_id) { + guard.push(user_id); + should_sync = true; + } + } + + if should_sync { + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.add_user_id(user_id as i64).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: Arc) { + 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() { + Some(weak_ref.clone()) + } else { + None + } + } + + /// Send a CommunicationValue to the Iota + pub async fn send_message(&self, cv: &CommunicationValue) { + if !cv.is_type(CommunicationType::pong) { + log_cv_out!(PrintType::Iota, cv); + } + if let Err(e) = self.sender.send(&cv).await { + log_err!( + self.iota_id as i64, + PrintType::Iota, + "Failed to send message: {:?}", + e + ); + } + } + + /// Handle incoming message from Iota + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + let msg_id = cv.get_id(); + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + if (task)(self.clone(), cv.clone()) { + return; + } + } + + // Handle ping + if cv.is_type(CommunicationType::ping) || cv.is_type(CommunicationType::pong) { + self.handle_ping(cv).await; + return; + } + + log_cv_in!(PrintType::Iota, cv); + + // Handle GET_CHATS + if cv.is_type(CommunicationType::get_chats) { + self.handle_get_chats(cv).await; + return; + } + + // Handle forwarding to other Iotas or clients + let receiver_id = cv.get_receiver(); + if (receiver_id != 0 && !self.get_user_ids().await.contains(&(receiver_id as u64))) + || cv.is_type(CommunicationType::message_other_iota) + || cv.is_type(CommunicationType::send_chat) + { + self.handle_forward_message(cv).await; + return; + } + + if cv.is_type(CommunicationType::change_iota_data) + || cv.is_type(CommunicationType::push_notification) + || cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::get_register) + || cv.is_type(CommunicationType::complete_register_user) + || cv.is_type(CommunicationType::delete_iota) + { + let sender = self.get_iota_id().await; + self.handle_omega_forward(cv.with_sender(sender as u64)) + .await; + return; + } + self.forward_to_client(cv).await; + } + + #[allow(dead_code)] + async fn send_error_response(&self, message_id: u32, error_type: CommunicationType) { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send_message(&error).await; + } + + #[allow(dead_code)] + async fn close(&self) { + let _ = self.sender.close(); + } + + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let iota_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.iota_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + iota_for_closure.send_message(&response_cv).await; + } + }); + } + /// Handle ping message + async fn handle_ping(&self, cv: CommunicationValue) { + if let DataValue::Number(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; + } + } + + let client_pings = if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.get_client_pings().await + } else { + HashMap::new() + }; + + let pings: Vec<(DataTypes, DataValue)> = client_pings + .into_iter() + .map(|(k, v)| (DataTypes::parse(k), DataValue::Number(v))) + .collect(); + let response = CommunicationValue::new(CommunicationType::pong) + .with_id(cv.get_id()) + .add_data(DataTypes::ping_clients, DataValue::Container(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(); + let sender_id = cv.get_sender(); + let my_user_ids = self.get_user_ids().await; + + log_in!( + self.iota_id as i64, + PrintType::Iota, + "Authority check: sender_id={} receiver_id={} iota_user_ids={:?} msg_type={:?} msg_id={}", + sender_id, + receiver_id, + my_user_ids, + cv.get_type(), + cv.get_id() + ); + + if my_user_ids.contains(&(sender_id as u64)) { + if let Some(target_rho) = rho_manager::get_rho_con_for_user(receiver_id as i64).await { + target_rho.message_to_iota(cv).await; + } else { + let error = CommunicationValue::new(CommunicationType::error_no_iota) + .with_id(cv.get_id()) + .with_sender(cv.get_sender()); + self.send_message(&error).await; + } + } else { + log_err!( + self.iota_id as i64, + PrintType::Iota, + "Rejected client->iota forward: sender_id={} is not authorized for this iota. Known users={:?}", + sender_id, + my_user_ids + ); + + self.send_message( + &CommunicationValue::new(CommunicationType::error_invalid_user_id).add_data( + DataTypes::error_type, + DataValue::Str( + "You are sending to another User without authority.".to_string(), + ), + ), + ) + .await; + } + } + + /// Handle GET_CHATS message + async fn handle_get_chats(&self, cv: CommunicationValue) { + let receiver_id = cv.get_receiver(); + let mut interested_ids: Vec = Vec::new(); + + // ============================ + // Load Calls + // ============================ + let calls: Vec> = call_manager::get_call_groups(receiver_id).await; + + let mut invites: HashMap> = HashMap::new(); + let empty = calls.is_empty(); + + for call in calls { + for inviter in call.members.read().await.iter() { + let call_self = call.get_caller(receiver_id).await.unwrap(); + + let inviter_id = inviter.user_id; + let timeout = *call_self.timeout.read().await; + let admin = call_self.has_admin(); + + // Build call container + let mut call_map: BTreeMap = BTreeMap::new(); + + call_map.insert(DataTypes::call_id, DataValue::Str(call.call_id.to_string())); + + if timeout > 0 { + call_map.insert(DataTypes::timeout, DataValue::Number(timeout as i64)); + } + + if admin { + call_map.insert(DataTypes::has_admin, DataValue::Bool(true)); + } + + let call_container = DataValue::container_from_map(&call_map); + + invites + .entry(inviter_id as i64) + .or_insert_with(Vec::new) + .push(call_container); + } + } + + // ============================ + // Enrich Contacts + // ============================ + let enriched_contacts = if empty { + match cv.get_data(DataTypes::user_ids) { + DataValue::Array(arr) => DataValue::Array(arr.clone()), + _ => DataValue::Array(vec![]), + } + } else { + let mut enriched: Vec = Vec::new(); + + if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { + for user_val in users { + if let DataValue::Container(entries) = user_val { + let mut user_map: BTreeMap = + entries.iter().cloned().collect(); + + // extract user_id + if let Some(DataValue::Number(user_id)) = user_map.get(&DataTypes::user_id) + { + interested_ids.push(*user_id); + + // attach calls if exists + if let Some(call_list) = invites.get(user_id) { + user_map + .insert(DataTypes::calls, DataValue::Array(call_list.clone())); + } + } + + enriched.push(DataValue::container_from_map(&user_map)); + } + } + } + + DataValue::Array(enriched) + }; + + // ============================ + // Notify Omega + // ============================ + OmegaConnection::user_states(receiver_id as i64, interested_ids.clone()).await; + + // ============================ + // Notify Rho + // ============================ + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn + .set_interested(receiver_id as i64, interested_ids) + .await; + } + + // ============================ + // Forward to client + // ============================ + self.forward_to_client(cv.add_data(DataTypes::user_ids, enriched_contacts)) + .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); + rho_conn.message_to_client(updated_cv).await; + } else { + } + } + + pub async fn handle_close(&self) { + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.close_iota_connection().await; + } + } + + #[allow(dead_code)] + pub async fn await_response( + self: Arc, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + let task_tx = tx.clone(); + self.waiting_tasks.insert( + msg_id, + Box::new(move |_, response_cv| { + let inner_tx = task_tx.clone(); + tokio::spawn(async move { + let _ = inner_tx.send(response_cv).await; + }); + true + }), + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(_) => Err("Failed to receive response, channel was closed.".to_string()), + Err(_) => { + self.waiting_tasks.remove(&msg_id); + Err(format!( + "Request timed out after {} seconds.", + timeout.as_secs() + )) + } + } + } +} + +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..ac498f3 --- /dev/null +++ b/src/rho/mod.rs @@ -0,0 +1,6 @@ +pub mod client_connection; +pub mod connection; +pub mod iota_connection; +pub mod rho_connection; +pub mod rho_manager; +pub mod server; diff --git a/src/rho/rho_connection.rs b/src/rho/rho_connection.rs new file mode 100644 index 0000000..6a740ff --- /dev/null +++ b/src/rho/rho_connection.rs @@ -0,0 +1,199 @@ +use super::{client_connection::ClientConnection, iota_connection::IotaConnection, rho_manager}; + +use crate::data::user::UserStatus; +use crate::omega::omega_connection::OmegaConnection; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; + +pub struct RhoConnection { + iota_connection: Arc, + user_ids: Arc>>, + 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: Arc::new(RwLock::new(user_ids.clone())), + client_connections: Arc::new(RwLock::new(Vec::new())), + }; + + rho_connection + } + + pub async fn get_iota_id(&self) -> u64 { + self.iota_connection.iota_id + } + + pub async fn get_user_ids(&self) -> Vec { + self.user_ids.read().await.clone() + } + + pub async fn set_user_ids(&self, user_ids: Vec) { + let mut guard = self.user_ids.write().await; + *guard = user_ids; + } + + pub async fn add_user_id(&self, user_id: i64) { + let mut guard = self.user_ids.write().await; + if !guard.contains(&user_id) { + guard.push(user_id); + } + } + + pub async fn bind_user_id(&self, user_id: i64) { + self.add_user_id(user_id).await; + self.iota_connection.add_user_id(user_id as u64).await; + } + + pub fn get_iota_connection(&self) -> &Arc { + &self.iota_connection + } + + 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: i64, + ) -> Vec> { + let connections = self.client_connections.read().await; + let mut collections = Vec::new(); + for con in connections.iter() { + if con.get_user_id().await == user_id as u64 { + collections.push(con.clone()); + } + } + collections + } + + /// Add a client connection + #[allow(dead_code)] + pub async fn add_client_connection(&self, connection: Arc) { + let notification = CommunicationValue::new(CommunicationType::client_connected).add_data( + DataTypes::user_id, + DataValue::Number(connection.get_user_id().await as i64), + ); + + self.iota_connection.send_message(¬ification).await; + + { + let mut connections = self.client_connections.write().await; + connections.push(Arc::clone(&connection)); + } + + OmegaConnection::client_changed( + self.get_iota_id().await as i64, + connection.get_user_id().await as i64, + UserStatus::user_online, + ) + .await; + } + + /// Remove a client connection + pub async fn close_client_connection(&self, connection: Arc) { + let target_user_id = connection.get_user_id().await; + { + let mut connections = self.client_connections.write().await; + connections.retain(|con| { + futures::executor::block_on(async { con.get_user_id().await != target_user_id }) + }); + } + + // Notify OmegaConnection + OmegaConnection::client_changed( + self.get_iota_id().await as i64, + connection.get_user_id().await as i64, + UserStatus::user_offline, + ) + .await; + } + + /// 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 as i64).await; + + // Notify OmegaConnection + OmegaConnection::close_iota(self.get_iota_id().await as i64).await; + } + + /// Send message from Iota to specific client + pub async fn message_to_client(&self, cv: CommunicationValue) { + let connections = self.client_connections.read().await; + let receiver_id = cv.get_receiver(); + for connection in connections.iter() { + if connection.get_user_id().await == receiver_id { + connection.clone().send_message(&cv).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: i64, interested_ids: Vec) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + let conn_user_id = connection.get_user_id().await; + if conn_user_id == user_id as u64 { + connection + .clone() + .set_interested_users(interested_ids.clone()) + .await; + break; + } + } + } + + /// Check if clients are interested in a user + #[allow(dead_code)] + pub async fn are_they_interested(&self, user_id: i64) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + connection.clone().are_you_interested(user_id).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() { + let 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 + #[allow(dead_code)] + pub async fn contains_user(&self, user_id: &i64) -> bool { + self.user_ids.read().await.contains(user_id) + } + + /// Get count of active client connections + #[allow(dead_code)] + pub async fn client_count(&self) -> usize { + let connections = self.client_connections.read().await; + connections.len() + } +} diff --git a/src/rho/rho_manager.rs b/src/rho/rho_manager.rs new file mode 100644 index 0000000..e823a04 --- /dev/null +++ b/src/rho/rho_manager.rs @@ -0,0 +1,83 @@ +use super::rho_connection::RhoConnection; +use crate::log_in; +use crate::util::logger::PrintType; +use std::{ + collections::HashMap, + sync::{Arc, LazyLock}, +}; +use tokio::sync::RwLock; + +pub static RHO_CONNECTIONS: LazyLock>>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn get_rho_con_for_user(user_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + for rho_connection in connections.values() { + let rho_user_ids = rho_connection.get_user_ids().await; + log_in!( + user_id, + PrintType::Client, + "Comparing user IDs: {:?}", + rho_user_ids + ); + if rho_user_ids.contains(&user_id) { + return Some(Arc::clone(rho_connection)); + } + } + None +} + +#[allow(dead_code)] +pub async fn contains_iota(iota_id: i64) -> bool { + let connections = RHO_CONNECTIONS.read().await; + connections.contains_key(&iota_id) +} + +/// Bind a user ID to an already tracked iota/rho connection. +pub async fn bind_user_to_iota(user_id: i64, iota_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + if let Some(rho_connection) = connections.get(&iota_id) { + let rho = Arc::clone(rho_connection); + drop(connections); + + rho.add_user_id(user_id).await; + + log_in!( + user_id, + PrintType::Client, + "Bound user {} to iota {}", + user_id, + iota_id + ); + + Some(rho) + } else { + None + } +} + +/// Remove a RhoConnection by Iota ID +pub async fn remove_rho(iota_id: i64) -> 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 as i64, rho_connection); +} + +/// Get a RhoConnection by Iota ID directly +#[allow(dead_code)] +pub async fn get_rho_by_iota(iota_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + connections.get(&iota_id).map(Arc::clone) +} + +/// Get the count of active connections +pub async fn connection_count() -> usize { + let connections = RHO_CONNECTIONS.read().await; + connections.len() +} diff --git a/src/rho/server.rs b/src/rho/server.rs new file mode 100644 index 0000000..9e3946d --- /dev/null +++ b/src/rho/server.rs @@ -0,0 +1,25 @@ +use crate::{ + log, + rho::connection::GeneralConnection, + util::{file_util::load_file_vec, logger::PrintType}, +}; +use ttp_native::Host; + +pub async fn start(port: u16) -> Result<(), Box> { + let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); + + let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); + + let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; + log!(0, PrintType::General, "Server listening on port {}", port); + + while let Some((sender, receiver)) = host.next().await { + tokio::spawn(async move { + let conn = GeneralConnection::new(sender, receiver); + conn.handle().await; + }); + } + log!(0, PrintType::General, "Server stopped"); + + Ok(()) +} diff --git a/src/server/api.rs b/src/server/api.rs deleted file mode 100644 index eb6f9cf..0000000 --- a/src/server/api.rs +++ /dev/null @@ -1,275 +0,0 @@ -use crate::get_public_key; -use crate::sql::sql; -use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; -use crate::transport::omikron_manager::get_random_omikron; -use crate::util::file_util::get_directory; -use crate::{ - sql::sql::{get_by_user_id, get_omikron_by_id}, - util::crypto_helper::public_key_to_base64, -}; -use actix_web::HttpResponse; -use actix_web::http::{StatusCode, header}; -use base64::Engine as _; -use json::JsonValue; - -pub async fn handle(path: &str, body_string: Option) -> HttpResponse { - if path == "OPTIONS" { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) - .insert_header(("Access-Control-Allow-Headers", "*")) - .finish(); - } - - let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); - - let _body: Option = if body_string.is_some() { - if let Ok(body_json) = json::parse(&body_string.unwrap()) { - Some(body_json) - } else { - None - } - } else { - None - }; - - let (status, body_text) = match path_parts.as_slice() { - // ================================================== - // DOWNLOAD IOTA FRONTEND - // ================================================== - ["api", "download", "iota_frontend"] => { - let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); - - match std::fs::read(file_path) { - Ok(file_bytes) => { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Content-Type", "application/zip")) - .insert_header(( - "Content-Disposition", - "attachment; filename=\"iota_frontend.zip\"", - )) - .body(file_bytes); - } - Err(_) => { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - return HttpResponse::NotFound() - .insert_header(("Access-Control-Allow-Origin", "*")) - .body(res.dump()); - } - } - } - - // ================================================== - // GET RANDOM OMIKRON - // ================================================== - ["api", "get", "omikron"] => { - if let Ok(omikron_conn) = get_random_omikron().await { - if let Some(id) = omikron_conn.get_omikron_id().await { - if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET OMIKRON BY ID - // ================================================== - ["api", "get", "omikron", id] => { - let id = id.parse::().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - (StatusCode::OK, res.dump()) - } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await - { - if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); - res["ip_address"] = ip_address.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET ID BY USERNAME - // ================================================== - ["api", "get", "id", username] => { - if username.is_empty() { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - _, - _, - _, - _, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // GET SERVER PUBLIC KEY - // ================================================== - ["api", "get", "public_key"] => { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["public_key"] = public_key_to_base64(&get_public_key()).into(); - (StatusCode::OK, res.dump()) - } - - // ================================================== - // GET USER BY ID - // ================================================== - ["api", "get", "user", id] => { - let id: i64 = id.parse().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - display, - status_msg, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - if let Some(display) = display { - res["display"] = display.into(); - } - if let Some(status_msg) = status_msg { - res["status_message"] = status_msg.into(); - } - if let Some(about) = about { - res["about"] = about.into(); - } - if let Some(avatar) = avatar { - res["avatar"] = base64::engine::general_purpose::STANDARD - .encode(avatar) - .into(); - } - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // DEFAULT - // ================================================== - _ => { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - }; - let body_bytes = body_text.into_bytes(); - - HttpResponse::build(status) - .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) - .body(body_bytes) -} diff --git a/src/server/mod.rs b/src/server/mod.rs deleted file mode 100644 index 8645e6e..0000000 --- a/src/server/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod api; -pub mod server; -pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs deleted file mode 100644 index 3618eb7..0000000 --- a/src/server/server.rs +++ /dev/null @@ -1,67 +0,0 @@ -use crate::{ - log, - server::{api, short_link::get_short_link}, - util::file_util::load_file_buf, -}; - -use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; - -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use rustls_pemfile::{certs, pkcs8_private_keys}; - -pub async fn start(port: u16) -> anyhow::Result<()> { - let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; - - let mut key_reader = load_file_buf("certs", "server_key.pem")?; - - let cert_chain: Vec> = - certs(&mut cert_reader).collect::>()?; - - let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) - .map(|res| res.map(Into::into)) - .collect::>()?; - - let key = keys.remove(0); - - let mut config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key)?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - let addr = format!("0.0.0.0:{port}"); - log!(" Server on {}", addr); - - HttpServer::new(move || { - App::new() - .route("/api/{path:.*}", web::to(api_handler)) - .route("/direct/{path:.*}", web::to(direct_handler)) - }) - .bind_rustls_0_23(addr, config)? - .run() - .await?; - - Ok(()) -} -async fn direct_handler(req: HttpRequest) -> impl Responder { - let path = req.uri().path().to_string(); - let short = path.replace("/direct/", ""); - - if let Ok(long) = get_short_link(&short).await { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, long)) - .finish() - } else { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, "https://tensamin.net")) - .finish() - } -} - -async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { - let path = req.uri().path().to_string(); - let body_string = String::from_utf8_lossy(&body).to_string(); - - api::handle(&path, Some(body_string)).await -} diff --git a/src/server/short_link.rs b/src/server/short_link.rs deleted file mode 100644 index c0ab51d..0000000 --- a/src/server/short_link.rs +++ /dev/null @@ -1,87 +0,0 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::{Rng, thread_rng}; - -static LINKS: Lazy> = Lazy::new(DashMap::new); - -const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; - -pub async fn add_short_link(long: &str) -> Result { - let raw = generate_unique_short_link().await; - LINKS.insert(raw.clone(), long.to_string()); - - Ok(format!( - "https://omega.tensamin.net/direct/{}", - format_with_dashes(&raw) - )) -} - -async fn generate_unique_short_link() -> String { - loop { - let short = generate_short_link().await; - if !LINKS.contains_key(&short) { - return short; - } - } -} - -pub async fn generate_short_link() -> String { - let len = short_length(); - - let mut rng = thread_rng(); - (0..len) - .map(|_| { - let idx = rng.gen_range(0..CHARSET.len()); - CHARSET[idx] as char - }) - .collect() -} - -pub async fn get_short_link(short: &str) -> Result { - let key = if short.contains("/") { - short.split("/").nth(1).unwrap_or_default() - } else { - short - }; - let frag = short.replace(key, ""); - let normalized = normalize_short(&key); - - if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { - Ok(format!("{}{}", t, frag)) - } else { - Err(()) - } -} - -/* ---------------- helpers ---------------- */ - -fn short_length() -> usize { - let count = LINKS.len(); - - match count { - 0..=1_999 => 4, - 2_000..=999_999 => 8, - _ => 12, - } -} - -fn format_with_dashes(s: &str) -> String { - s.chars() - .collect::>() - .chunks(4) - .map(|c| c.iter().collect::()) - .collect::>() - .join("-") -} - -fn normalize_short(input: &str) -> String { - input - .chars() - .filter(|c| *c != '-') - .map(|c| match c { - 'Q' | 'O' => '0', - 'I' => 'l', - _ => c, - }) - .collect() -} diff --git a/src/sql/mod.rs b/src/sql/mod.rs deleted file mode 100644 index 91ba841..0000000 --- a/src/sql/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod connection_status; -pub mod sql; -pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs deleted file mode 100644 index 8445e26..0000000 --- a/src/sql/sql.rs +++ /dev/null @@ -1,765 +0,0 @@ -use crate::log; -use once_cell::sync::Lazy; -use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::{ - env, - sync::Arc, - time::{SystemTime, UNIX_EPOCH}, -}; -use tokio::sync::RwLock; - -/* -use crate::sql::{ - iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, - sql::{ - change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, - change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, - get_register_id, register_complete_iota, register_complete_user, - }, -}; - */ - -static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); - -pub async fn connect() -> Result, sqlx::Error> { - let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); - let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); - let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); - - MySqlPoolOptions::new() - .max_connections(200) - .connect(&format!( - "mysql://{}:{}@127.0.0.1:3306/{}", - user, passwd, table - )) - .await -} -// Omega -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -pub async fn initialize_db() -> Result<(), sqlx::Error> { - let pool = connect().await?; - let mut db_lock = SQL_DB.write().await; - // create tables - // with indexes - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - users ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, - display VARCHAR(15) COLLATE utf8mb4_bin, - status VARCHAR(15) COLLATE utf8mb4_bin, - about VARCHAR(200) COLLATE utf8mb4_bin, - avatar MEDIUMBLOB, - sub_level INT(11) NOT NULL DEFAULT 0, - sub_end BIGINT(20) NOT NULL DEFAULT 0, - public_key TEXT NOT NULL COLLATE utf8mb4_bin, - private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', - iota_id BIGINT UNSIGNED NOT NULL, - token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - iotas ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - omikrons ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - notifications ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, - sender_id BIGINT UNSIGNED NOT NULL, - receiver_id BIGINT UNSIGNED NOT NULL, - amount BIGINT UNSIGNED NOT NULL DEFAULT 0 - )", - ) - .execute(&pool) - .await; - *db_lock = Some(pool); - Ok(()) -} - -// ========================================================================================== -// REGISTER -// ========================================================================================== -pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); -pub static CURRENT_REGISTER_PROCESS: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); - -pub async fn get_register_id() -> u64 { - let mut current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as u64; - - loop { - let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); - - if current_locked < current_time { - let result = CURRENT_MILLI_USED.compare_exchange( - current_locked, // expected value - current_time, // new value - Ordering::SeqCst, // acquire/release ordering - Ordering::SeqCst, // failure ordering - ); - - match result { - Ok(_) => { - CURRENT_REGISTER_PROCESS.write().await.push(current_time); - return current_time; - } - Err(_) => { - continue; - } - } - } else { - current_time = current_locked + 1; - } - } -} - -// ========================================================================================== -// USERS -// ========================================================================================== - -pub async fn get_by_username( - username: &str, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", - ) - .bind(username) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - username, - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_by_user_id( - id: i64, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - username, - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_users_by_iota_id( - iota_id_param: i64, -) -> Result< - Vec<( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - )>, - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", - ) - .bind(iota_id_param) - .fetch_all(&pool) - .await?; - - let mut users = Vec::new(); - for row in rows { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - users.push(( - id, - iota_id, - username, - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - private_key_hash, - String::from_utf8_lossy(&token).to_string(), - )); - } - - Ok(users) -} - -pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_username) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_display) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_avatar) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_about) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_status) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") - .bind(new_iota_id) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_keys( - id: i64, - new_public_key: String, - new_private_key_hash: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query( - "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(new_public_key) - .bind(new_private_key_hash) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_token) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn register_complete_user( - id: i64, - username: String, - public_key: String, - iota_id: i64, - token: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query( - "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", - ) - .bind(id) - .bind(username) - .bind(public_key) - .bind(iota_id) - .bind(token) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn print_users() -> Result<(), Box> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - log!("Printing users..."); - for row in sqlx::query( - "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", - ) - .fetch_all(&pool) - .await? - .iter() - { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - - log!( - "User: {:?}", - ( - id, - iota_id, - username, - display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), - status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), - about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), - sub_level, - sub_end - ) - ); - } - - Ok(()) -} - -// ========================================================================================== -// IOTA -// ========================================================================================== -pub async fn create_new_iota(public_key: String) -> Result { - let new_id = get_register_id().await as i64; - register_complete_iota(new_id, public_key).await?; - Ok(new_id) -} - -pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") - .bind(id) - .bind(public_key) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let result = sqlx::query_as::<_, (u64, Vec)>( - "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await; - - match result { - Ok(optional_row) => match optional_row { - Some((id_u64, public_key)) => Ok(( - id_u64 as i64, - String::from_utf8_lossy(&public_key).to_string(), - )), - _ => Err(sqlx::Error::RowNotFound), - }, - Err(e) => Err(e), - } -} - -pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") - .bind(new_key) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -// ========================================================================================== -// OMIKRONS -// ========================================================================================== - -pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some((public_key, ip_address)) => Ok(( - String::from_utf8_lossy(&public_key).to_string(), - String::from_utf8_lossy(&ip_address).to_string(), - )), - _ => Err(sqlx::Error::RowNotFound), - } -} - -// ========================================================================================== -// PHI -// ========================================================================================== - -pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - INSERT INTO notifications (sender_id, receiver_id, amount) - VALUES (?, ?, 1) - ON DUPLICATE KEY UPDATE amount = amount + 1 - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query_as::<_, (i64, i64)>( - r#" - SELECT sender_id, amount FROM notifications WHERE receiver_id = ? - "#, - ) - .bind(user_id) - .fetch_all(pool) - .await -} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs deleted file mode 100644 index 42dfa22..0000000 --- a/src/sql/user_online_tracker.rs +++ /dev/null @@ -1,144 +0,0 @@ -use crate::sql; -use crate::sql::connection_status::UserStatus; -use dashmap::DashMap; -use once_cell::sync::Lazy; - -#[derive(Debug, Clone)] -pub struct UserConnection { - pub connection_type: UserStatus, - pub omikron_id: i64, -} - -// IotaID -> Primary OmikronID -static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); - -// IotaID -> Vec -static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); - -// UserID -> UserStatus -static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); - -pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { - let mut entry = IOTA_OMIKRON_CONNECTIONS - .entry(iota_id) - .or_insert_with(Vec::new); - - if !entry.contains(&omikron_id) { - entry.push(omikron_id); - } - - if primary { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); - } -} - -pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { - let mut vec = r.value().clone(); - vec.retain(|&id| id != omikron_id); - let empty = vec.is_empty(); - drop(r); - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); - empty - } else { - false - }; - - if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { - let primary_id = *primary_ref.value(); - drop(primary_ref); - if primary_id == omikron_id { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } - } - - if connections_empty { - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); - } - - connections_empty -} - -pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { - IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) -} - -pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { - IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) -} - -pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { - USER_STATUS_MAP.insert( - user_id, - UserConnection { - connection_type: status, - omikron_id, - }, - ); -} - -pub fn get_user_status(user_id: i64) -> Option { - USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) -} - -pub fn untrack_many_users(user_ids: &[i64]) { - for user_id in user_ids { - USER_STATUS_MAP.remove(user_id); - } -} - -pub async fn untrack_omikron(omikron_id: i64) { - let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION - .iter() - .filter(|entry| *entry.value() == omikron_id) - .map(|entry| *entry.key()) - .collect(); - - for key in primary_keys_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); - } - - let mut offline_iotas = Vec::new(); - let mut primary_to_remove = Vec::new(); - - // Collect iotas and primary info first - for r in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *r.key(); - let mut connections = r.value().clone(); - connections.retain(|&id| id != omikron_id); - - if connections.is_empty() { - offline_iotas.push(iota_id); - } - - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) - { - primary_to_remove.push(iota_id); - } - - // Update the connections vector after filtering - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); - } - - // Step 2: Remove primary connections safely - for iota_id in primary_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } - - // Step 3: Remove users that were on this omikron - USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - - // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP - for iota_id in offline_iotas { - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { - for user in users { - USER_STATUS_MAP.remove(&user.0); - } - } - // Finally remove the empty connections vector - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); - } -} diff --git a/src/transport/mod.rs b/src/transport/mod.rs deleted file mode 100644 index 4f3953a..0000000 --- a/src/transport/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod omikron_connection; -pub mod omikron_manager; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs deleted file mode 100644 index 504d723..0000000 --- a/src/transport/omikron_connection.rs +++ /dev/null @@ -1,1139 +0,0 @@ -use crate::{ - get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, - server::short_link::add_short_link, - sql::{ - connection_status::UserStatus, - sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, - user_online_tracker::{self}, - }, - transport::omikron_manager, - util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use dashmap::DashMap; -use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use epsilon_native::{Host, Receiver, Sender}; -use rand::{Rng, distributions::Alphanumeric}; -use std::{ - sync::Arc, - time::{Duration, Instant}, -}; -use tokio::{ - sync::{Mutex, RwLock}, - time::interval, -}; -use x448::PublicKey; - -// ============================================================================ -// Configuration -// ============================================================================ - -const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); -const MAX_WAITING_AGE: Duration = Duration::from_secs(60); - -// ============================================================================ -// Error Types -// ============================================================================ - -#[derive(Debug, thiserror::Error)] -pub enum OmikronError { - #[error("Not connected")] - NotConnected, - #[error("Not authenticated")] - NotAuthenticated, - #[error("Invalid response")] - InvalidResponse, - #[error("Authentication failed")] - AuthenticationFailed, - #[error("SQL error: {0}")] - Sql(String), - #[error("Send error: {0}")] - Send(String), -} - -pub type OmikronResult = Result; - -// ============================================================================ -// Waiting Task System (Preserved from original) -// ============================================================================ - -pub struct WaitingTask { - pub task: Box, CommunicationValue) -> bool + Send + Sync>, - pub inserted_at: Instant, -} - -// ============================================================================ -// Connection State -// ============================================================================ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum AuthState { - Unauthenticated, - Identified { omikron_id: i64 }, - Authenticated { omikron_id: i64 }, -} - -impl AuthState { - fn is_authenticated(&self) -> bool { - match self { - AuthState::Authenticated { omikron_id } => true, - _ => false, - } - } - - fn omikron_id(&self) -> Option { - match self { - AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { - Some(*omikron_id) - } - _ => None, - } - } -} - -// ============================================================================ -// Omikron Connection (Epsilon/QUIC-based) -// ============================================================================ - -pub struct OmikronConnection { - id: u64, - sender: Mutex>, - state: RwLock, - challenge: RwLock, - pub_key: RwLock>>, - pub ping: RwLock, - waiting_tasks: DashMap, - cleanup_handle: Mutex>>, -} - -impl OmikronConnection { - // ------------------------------------------------------------------------- - // Construction - // ------------------------------------------------------------------------- - - pub fn new(sender: Sender) -> Arc { - let conn = Arc::new(Self { - id: rand::random(), - sender: Mutex::new(Some(sender)), - state: RwLock::new(AuthState::Unauthenticated), - challenge: RwLock::new(String::new()), - pub_key: RwLock::new(None), - ping: RwLock::new(-1), - waiting_tasks: DashMap::new(), - cleanup_handle: Mutex::new(None), - }); - - conn - } - - // ------------------------------------------------------------------------- - // Main Handler Loop - // ------------------------------------------------------------------------- - - pub async fn handle(self: Arc, receiver: &mut Receiver) { - log_in!( - self.id as i64, - PrintType::Omega, - "Omikron connection started" - ); - - // Start cleanup task - let cleanup_conn = self.clone(); - let cleanup_handle = tokio::spawn(async move { - let mut ticker = interval(CLEANUP_INTERVAL); - loop { - ticker.tick().await; - cleanup_conn - .waiting_tasks - .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); - } - }); - *self.cleanup_handle.lock().await = Some(cleanup_handle); - - while let Ok(cv) = receiver.receive().await { - if let Err(e) = self.clone().process_message(cv).await { - log_err!(0, PrintType::Omega, "Error processing message: {}", e); - if matches!(e, OmikronError::NotConnected) { - break; - } - } - } - - self.clone().cleanup().await; - log_in!( - self.id as i64, - PrintType::Omega, - "Omikron connection closed" - ); - } - - // ------------------------------------------------------------------------- - // Message Processing - // ------------------------------------------------------------------------- - - async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_in!(PrintType::Omikron, &cv); - } - - let msg_id = cv.get_id(); - - // Check waiting tasks first (response to previous request) - if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { - let _ = (task.task)(self.clone(), cv); - return Ok(()); - } - - // Handle ping regardless of auth state - if cv.is_type(CommunicationType::ping) { - return self.handle_ping(cv).await; - } - - let current_state = *self.state.read().await; - // Route based on authentication state - match current_state { - AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, - AuthState::Identified { .. } => self.clone().handle_identified(cv).await, - AuthState::Authenticated { omikron_id } => { - self.clone().handle_authenticated(cv, omikron_id).await - } - } - } - - // ------------------------------------------------------------------------- - // Authentication Handlers - // ------------------------------------------------------------------------- - - async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::identification) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - // Extract omikron ID - let omikron_id = cv - .get_data(DataTypes::omikron_id) - .as_number() - .ok_or(OmikronError::InvalidResponse)?; - log!("Omikron {:?} connected", omikron_id); - - // Lookup omikron in database - let (public_key, _) = get_omikron_by_id(omikron_id) - .await - .map_err(|e| OmikronError::Sql(e.to_string()))?; - - log!("Got public Key"); - - let pub_key_bytes = STANDARD - .decode(&public_key) - .map_err(|_| OmikronError::AuthenticationFailed)?; - - let pub_key_bytes_clone = pub_key_bytes.clone(); - let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) - .ok_or(OmikronError::AuthenticationFailed)?; - - log!("Decoded public Key"); - - // Generate challenge - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - log!("Generated Challenge"); - - *self.challenge.write().await = challenge.clone(); - - log!("Stored Challenge"); - - *self.pub_key.write().await = Some(pub_key_bytes); - - log!("Stored Pubkey"); - - *self.state.write().await = AuthState::Identified { omikron_id }; - - log!("Stored State"); - - let challenge_clone = challenge.clone(); - let private_key = get_private_key(); - let public_key_for_encrypt = omikron_pub_key; - - let encrypted = tokio::task::spawn_blocking(move || { - encrypt(private_key, public_key_for_encrypt, &challenge_clone) - .map_err(|_| OmikronError::AuthenticationFailed) - }) - .await - .map_err(|_| OmikronError::AuthenticationFailed)??; - - log!("Encrypted Challenge"); - - // Send challenge response - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted)); - - log!("Sending Challenge"); - self.send(&response).await - } - - async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::challenge_response) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - let client_response = cv - .get_data(DataTypes::challenge) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let expected_challenge = self.challenge.read().await.clone(); - - if client_response == expected_challenge { - let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); - *self.state.write().await = AuthState::Authenticated { omikron_id }; - - omikron_manager::add_omikron(self.clone()).await; - - let response = CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::Bool(true)); - - self.clone().send(&response).await?; - log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); - Ok(()) - } else { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) - .await; - Err(OmikronError::AuthenticationFailed) - } - } - - // ------------------------------------------------------------------------- - // Authenticated Message Handlers - // ------------------------------------------------------------------------- - - async fn handle_authenticated( - self: Arc, - cv: CommunicationValue, - omikron_id: i64, - ) -> OmikronResult<()> { - match cv.get_type() { - // Link shortening - CommunicationType::shorten_link => self.handle_shorten_link(cv).await, - - // Online status tracking - CommunicationType::user_connected => { - self.handle_user_connected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::user_disconnected => { - self.handle_user_disconnected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::iota_connected => { - self.handle_iota_connected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::iota_disconnected => { - self.handle_iota_disconnected(cv, omikron_id).await; - Ok(()) - } - CommunicationType::sync_client_iota_status => { - self.handle_sync_status(cv, omikron_id).await; - Ok(()) - } - - CommunicationType::get_user_data => self.handle_get_user_data(cv).await, - CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, - - CommunicationType::get_register => self.handle_get_register(cv).await, - CommunicationType::complete_register_iota => { - self.handle_complete_register_iota(cv).await - } - CommunicationType::complete_register_user => { - self.handle_complete_register_user(cv).await - } - - CommunicationType::change_user_data => self.handle_change_user_data(cv).await, - CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, - CommunicationType::delete_user => self.handle_delete_user(cv).await, - CommunicationType::delete_iota => self.handle_delete_iota(cv).await, - - CommunicationType::get_notifications => self.handle_get_notifications(cv).await, - CommunicationType::read_notification => self.handle_read_notification(cv).await, - CommunicationType::push_notification => self.handle_push_notification(cv).await, - - _ => { - log_err!( - 0, - PrintType::Omega, - "Unknown message type: {:?}", - cv.get_type() - ); - Ok(()) - } - } - } - - // ------------------------------------------------------------------------- - // Specific Handlers (ported from original WebSocket implementation) - // ------------------------------------------------------------------------- - - async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let link = cv - .get_data(DataTypes::link) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let short = add_short_link(link) - .await - .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; - - let response = CommunicationValue::new(CommunicationType::shorten_link) - .with_id(cv.get_id()) - .add_data(DataTypes::link, DataValue::Str(short)); - - self.send(&response).await - } - - async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_online, - omikron_id, - ); - } - } - - async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { - log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { - user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_offline, - status.omikron_id, - ); - } - } - } - - async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA connected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - - let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id)); - user_online_tracker::track_user_status( - user_id, - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error loading users for IOTA"); - } - - let response = CommunicationValue::new(CommunicationType::iota_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); - - let _ = self.send(&response).await; - } else { - log_in!(PrintType::General, "No IOTA ID found"); - } - } - - async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA disconnected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } - } - } - } - - async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { - if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { - for user_id_val in user_ids { - if let DataValue::Number(user_id) = user_id_val { - user_online_tracker::track_user_status( - *user_id, - UserStatus::user_online, - omikron_id, - ); - } - } - } - - if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { - for iota_id_val in iota_ids { - if let DataValue::Number(iota_id) = iota_id_val { - user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); - } - } - } - } - - async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by user_id first - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - if let Ok(user_data) = get_by_user_id(user_id as i64).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { - if let Ok(user_data) = get_by_username(username).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - // Not found - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_user_data_response( - self: Arc, - msg_id: u32, - user: ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - String, - String, - String, - ), - ) -> CommunicationValue { - let ( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - ) = user; - - let mut response = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(msg_id) - .add_data(DataTypes::username, DataValue::Str(username.clone())) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::user_id, DataValue::Number(id)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) - .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) - .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); - - // Display name (fallback to username) - let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); - response = response.add_data(DataTypes::display, DataValue::Str(display_name)); - - // Optional fields - if let Some(s) = status.filter(|s| !s.is_empty()) { - response = response.add_data(DataTypes::status, DataValue::Str(s)); - } - if let Some(a) = about.filter(|a| !a.is_empty()) { - response = response.add_data(DataTypes::about, DataValue::Str(a)); - } - if let Some(av) = avatar { - response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); - } - - // Online status - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - if let Some(us) = user_status { - response = response.add_data( - DataTypes::online_status, - DataValue::Str(us.connection_type.to_string()), - ); - response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); - } else { - response = response.add_data( - DataTypes::online_status, - DataValue::Str(UserStatus::iota_offline.to_string()), - ); - } - - response = response.add_data( - DataTypes::omikron_connections, - DataValue::Array( - iota_connections - .into_iter() - .map(DataValue::Number) - .collect(), - ), - ); - - response - } - - async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by iota_id - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; - } - } - - // Try by user_id - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(user_id as i64).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id as i64), - None, - ) - .await; - return self.send(&response).await; - } - } - } - - // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { - if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_username(username).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id), - Some(username.to_string()), - ) - .await; - return self.send(&response).await; - } - } - } - - let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_iota_data_response( - self: Arc, - msg_id: u32, - iota_id: i64, - public_key: String, - user_id: Option, - username: Option, - ) -> CommunicationValue { - let mut response = CommunicationValue::new(CommunicationType::get_iota_data) - .with_id(msg_id) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); - - if let Some(uid) = user_id { - response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); - } - if let Some(uname) = username { - response = response.add_data(DataTypes::username, DataValue::Str(uname)); - } - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - response.add_data( - DataTypes::omikron_connections, - DataValue::Array( - iota_connections - .into_iter() - .map(DataValue::Number) - .collect(), - ), - ) - } - - async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::get_register) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); - self.send(&response).await - } - - async fn handle_complete_register_iota( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let iota_id_opt = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); - - if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { - if let Some(iota_id) = iota_id_opt { - // Register existing IOTA - match sql::register_complete_iota(iota_id, public_key.to_string()).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - // Create new IOTA - match sql::create_new_iota(public_key.to_string()).await { - Ok(new_iota_id) => { - let response = - CommunicationValue::new(CommunicationType::complete_register_iota) - .with_id(cv.get_id()) - .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_complete_register_user( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv - .get_data(DataTypes::user_id) - .as_number() - .map(|n| n as i64); - let username = cv - .get_data(DataTypes::username) - .as_str() - .map(|s| s.to_string()); - let public_key = cv - .get_data(DataTypes::public_key) - .as_str() - .map(|s| s.to_string()); - let iota_id = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); - let reset_token = cv - .get_data(DataTypes::reset_token) - .as_str() - .map(|s| s.to_string()); - - if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = - (user_id, username, public_key, iota_id, reset_token) - { - match sql::register_complete_user(uid, uname, pk, iid, rt).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - let mut success = true; - let mut error_message = String::new(); - - // Process each field - if let Some(username) = cv.get_data(DataTypes::username).as_str() { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(display) = cv.get_data(DataTypes::display).as_str() { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(about) = cv.get_data(DataTypes::about).as_str() { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(status) = cv.get_data(DataTypes::status).as_str() { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataTypes::public_key).as_str(), - cv.get_data(DataTypes::private_key_hash).as_str(), - ) { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } else { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); - self.send(&response).await - } - } - - async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - - if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64), - cv.get_data(DataTypes::reset_token).as_str(), - cv.get_data(DataTypes::new_token).as_str(), - ) { - match sql::get_by_user_id(user_id).await { - Ok(user) => { - let current_token = user.11; // reset_token field - if current_token == reset_token { - let mut success = true; - let mut error_message = String::new(); - - if let Err(e) = sql::change_iota_id(user_id, iota_id).await { - success = false; - error_message = e.to_string(); - } - if success { - if let Err(e) = sql::change_token(user_id, new_token.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::success) - .with_id(cv.get_id()); - self.send(&response).await - } else { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); - self.send(&response).await - } - } else { - self.send_error_response( - cv.get_id(), - CommunicationType::error_invalid_challenge, - ) - .await - } - } - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error_not_found) - .await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - match sql::delete_user(user_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - - async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(iota_id) = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64) - { - match sql::delete_iota(iota_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await - } - } - - async fn handle_get_notifications( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Ok(notifications) = sql::get_notifications(user_id).await { - let json_array: Vec = notifications - .into_iter() - .map(|(sender, amount)| { - DataValue::Container(vec![ - (DataTypes::sender_id, DataValue::Number(sender)), - (DataTypes::amount, DataValue::Number(amount)), - ]) - }) - .collect(); - - let response = CommunicationValue::new(CommunicationType::get_notifications) - .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(json_array)); - self.send(&response).await - } else { - Ok(()) - } - } - - async fn handle_read_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Some(other_id) = cv - .get_data(DataTypes::sender_id) - .as_number() - .map(|n| n as i64) - { - if sql::read_notification(user_id, other_id).await.is_ok() { - let response = CommunicationValue::new(CommunicationType::read_notification) - .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) - } - } else { - Ok(()) - } - } - - async fn handle_push_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Some(other_id) = cv - .get_data(DataTypes::sender_id) - .as_number() - .map(|n| n as i64) - { - if sql::add_notification(user_id, other_id).await.is_ok() { - let response = CommunicationValue::new(CommunicationType::push_notification) - .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) - } - } else { - Ok(()) - } - } - - async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - *self.ping.write().await = *last_ping; - } - - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); - self.send(&response).await - } - - // ------------------------------------------------------------------------- - // Utilities - // ------------------------------------------------------------------------- - - async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_out!(PrintType::Omikron, cv); - } - - let guard = self.sender.lock().await; - let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; - - sender - .send(cv) - .await - .map_err(|e| OmikronError::Send(e.to_string())) - } - - async fn send_error_response( - self: Arc, - message_id: u32, - error_type: CommunicationType, - ) -> OmikronResult<()> { - let error = CommunicationValue::new(error_type).with_id(message_id); - self.send(&error).await - } - - pub async fn close(self: Arc) { - log_in!( - self.get_omikron_id().await.unwrap_or(0), - PrintType::Omega, - "Omikron connection Closed" - ); - } - - async fn cleanup(self: Arc) { - if let Some(omikron_id) = self.state.read().await.omikron_id() { - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } - } - - // Cancel cleanup task - if let Some(handle) = self.cleanup_handle.lock().await.take() { - handle.abort(); - } - } - - // Public API for external use - pub async fn is_authenticated(self: Arc) -> bool { - self.state.read().await.is_authenticated() - } - - pub async fn get_omikron_id(self: Arc) -> Option { - self.state.read().await.omikron_id() - } - - pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - self.send(cv).await - } -} - -// ============================================================================ -// Server Startup -// ============================================================================ - -pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - - let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; - log!("OmikronServer listening on port {}", port); - - while let Some((sender, mut receiver)) = host.next().await { - tokio::spawn(async move { - let conn = OmikronConnection::new(sender); - conn.handle(&mut receiver).await; - }); - } - - Ok(()) -} diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs deleted file mode 100644 index fbee4a9..0000000 --- a/src/transport/omikron_manager.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::transport::omikron_connection::OmikronConnection; -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::prelude::IteratorRandom; -use std::sync::Arc; - -pub static OMIKRON_CONNECTIONS: Lazy>> = - Lazy::new(|| DashMap::new()); - -pub async fn add_omikron(conn: Arc) { - let id = match conn.clone().get_omikron_id().await { - Some(id) => id, - _ => { - conn.close().await; - return; - } - }; - - if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { - old.close().await; - } -} - -pub async fn remove_omikron(omikron_id: i64) { - OMIKRON_CONNECTIONS.remove(&omikron_id); -} -pub async fn get_random_omikron() -> Result, ()> { - let mut rng = rand::thread_rng(); - - let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); - - if let Some(key) = keys.into_iter().choose(&mut rng) { - if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { - return Ok(entry.clone()); - } - } - - Err(()) -} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs old mode 100644 new mode 100755 index 68e3855..cb3ae6a --- a/src/util/crypto_helper.rs +++ b/src/util/crypto_helper.rs @@ -8,27 +8,36 @@ use sha2::{Digest, Sha256}; use x448::{PublicKey, Secret, SharedSecret}; /// Errors for crypto operations +#[allow(dead_code)] #[derive(Debug)] pub enum CryptoError { - Base64Decode, + Base64Decode(base64::DecodeError), + InvalidKey, AgreementError, EncryptionError(aes_gcm::Error), DecryptionError(aes_gcm::Error), } impl From for CryptoError { - fn from(_: base64::DecodeError) -> Self { - CryptoError::Base64Decode + fn from(err: base64::DecodeError) -> Self { + CryptoError::Base64Decode(err) } } -pub fn generate_keypair() -> (Secret, PublicKey) { +#[allow(dead_code)] +pub struct KeyPair { + pub secret: Secret, + pub public: PublicKey, +} + +#[allow(dead_code)] +pub fn generate_keypair() -> KeyPair { let mut buf = [0u8; 56]; let mut rng = OsRng; rng.fill_bytes(&mut buf); let secret = Secret::from_bytes(&buf).unwrap(); let public = PublicKey::from(&secret); - (secret, public) + KeyPair { secret, public } } pub fn public_key_to_base64(pubkey: &PublicKey) -> String { @@ -58,6 +67,7 @@ fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { key } +#[allow(dead_code)] pub fn encrypt_b64( base64_secret: &str, base64_peer_pub: &str, @@ -125,12 +135,14 @@ pub fn decrypt( Ok(plaintext) } +#[allow(dead_code)] pub fn hash_it(input: &str) -> Vec { let mut hasher = Sha256::new(); hasher.update(input.as_bytes()); hasher.finalize().to_vec() } +#[allow(dead_code)] pub fn hex_hash(input: &str) -> String { let digest = hash_it(input); digest.iter().map(|b| format!("{:02x}", b)).collect() diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs index 97acab4..bb784e8 100644 --- a/src/util/crypto_util.rs +++ b/src/util/crypto_util.rs @@ -4,11 +4,12 @@ use aes_gcm::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; use hkdf::Hkdf; -use sha2::{Digest, Sha256}; +type HkdfSha256 = sha2::Sha256; +use sha2::{Digest, Sha256 as HashSha256}; use x448::{PublicKey, Secret}; -// --- Custom Errors --- #[derive(Debug)] +#[allow(dead_code)] pub enum SecurePayloadError { InvalidBase64, InvalidHex, @@ -17,19 +18,16 @@ pub enum SecurePayloadError { InvalidKeyLength, } -// --- Data Format Enum --- #[derive(Clone, Copy, Debug)] +#[allow(dead_code)] pub enum DataFormat { Raw, Base64, Hex, } -// --- Main Class Structure --- pub struct SecurePayload { - /// The internal canonical representation is always raw bytes. inner_data: Vec, - /// The private key of the user associated with this payload instance. private_key: Secret, } @@ -42,8 +40,8 @@ impl Clone for SecurePayload { } } +#[allow(dead_code)] impl SecurePayload { - /// Clear Constructor: Takes data in any format and the user's private key. pub fn new>( data: T, format: DataFormat, @@ -68,12 +66,10 @@ impl SecurePayload { }) } - /// Helper to get the public key associated with this instance's private key. pub fn get_public_key(&self) -> [u8; 56] { *PublicKey::from(&self.private_key).as_bytes() } - /// Exports the internal data to the requested format pub fn export(&self, format: DataFormat) -> String { match format.into() { DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), @@ -82,14 +78,12 @@ impl SecurePayload { } } - /// Access raw bytes directly pub fn get_bytes(&self) -> &[u8] { &self.inner_data } - /// Returns the SHA-256 Hash of the data in the requested format pub fn get_hash(&self, format: DataFormat) -> String { - let mut hasher = Sha256::new(); + let mut hasher = HashSha256::new(); hasher.update(&self.inner_data); let result = hasher.finalize(); @@ -100,8 +94,6 @@ impl SecurePayload { } } - /// Encrypts the held data for a specific recipient using AES-256-GCM. - /// The message will contain ONLY the ciphertext. pub fn encrypt_x448(&self, public_key: S) -> Result where S: Into, @@ -109,22 +101,15 @@ impl SecurePayload { let peer_pub = public_key.into(); let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - println!( - "Encryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; - // 3. Key & Nonce Derivation (HKDF) - // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) .map_err(|_| SecurePayloadError::EncryptionError)?; let key = &okm[..32]; let nonce_bytes = &okm[32..]; - // 4. Encrypt with AES-256-GCM let cipher = Aes256Gcm::new(key.into()); let nonce = Nonce::from_slice(nonce_bytes); @@ -138,40 +123,33 @@ impl SecurePayload { ) .map_err(|_| SecurePayloadError::EncryptionError)?; - // 5. Result is ONLY the ciphertext. No key or nonce is packed. Ok(SecurePayload { inner_data: ciphertext, private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), }) } - /// Decrypts the held data providing the sender's public key manually. pub fn decrypt_to_format( &self, peer_public_key_bytes: &[u8; 56], output_format: DataFormat, ) -> Result { - let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; + let decrypted_instance = + self.decrypt_x448(PublicKey::from_bytes(peer_public_key_bytes).unwrap())?; Ok(decrypted_instance.export(output_format)) } - /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. - pub fn decrypt_x448( + pub fn decrypt_x448( &self, - peer_public_key_bytes: &[u8; 56], - ) -> Result { - // 1. Perform Exchange - let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); + peer_public_key_bytes: S, + ) -> Result + where + S: Into, + { + let peer_pub = peer_public_key_bytes.into(); let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - // LOGGING: Shared Secret - println!( - "Decryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 2. Key & Nonce Derivation (Must match encryption exactly) - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); let mut okm = [0u8; 44]; hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) .map_err(|_| SecurePayloadError::DecryptionError)?; @@ -179,7 +157,6 @@ impl SecurePayload { let key = &okm[..32]; let nonce_bytes = &okm[32..]; - // 3. Decrypt with AES-256-GCM let cipher = Aes256Gcm::new(key.into()); let nonce = Nonce::from_slice(nonce_bytes); diff --git a/src/util/file_util.rs b/src/util/file_util.rs index b108810..cd214b1 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -1,20 +1,9 @@ use std::fs::{self, File}; use std::io::{self, BufReader, Read}; use std::path::{Path, PathBuf}; -use tokio::io::AsyncWriteExt; -use uuid::Uuid; -use zip::ZipArchive; use crate::log; - -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() -} +use crate::util::logger::PrintType; #[allow(dead_code)] pub fn delete_directory(path: &str) -> bool { @@ -29,9 +18,11 @@ fn delete_dir_recursive(directory: &Path) -> bool { } if let Err(e) = fs::remove_dir_all(directory) { log!( + 0, + PrintType::General, "[IMPORTANT] Couldn't delete directory {}: {}", directory.display(), - e + e, ); return false; } @@ -39,13 +30,14 @@ fn delete_dir_recursive(directory: &Path) -> bool { } #[allow(dead_code)] -pub fn delete_user_directory(user_id: Uuid) { +pub fn delete_user_directory(user_id: i64) { let user_dir = Path::new(&get_directory()) .join("users") .join(user_id.to_string()); let _ = delete_dir_recursive(&user_dir); } +#[allow(dead_code)] pub fn load_file_buf(path: &str, name: &str) -> io::Result> { let dir = Path::new(&get_directory()).join(path); let file_path = dir.join(name); @@ -72,6 +64,7 @@ pub fn load_file_buf(path: &str, name: &str) -> io::Result> { let file = File::open(&file_path)?; Ok(BufReader::new(file)) } +#[allow(dead_code)] pub fn has_file(path: &str, name: &str) -> bool { let dir = Path::new(&get_directory()).join(path); let file_path = dir.join(name); @@ -86,6 +79,7 @@ pub fn has_file(path: &str, name: &str) -> bool { true } +#[allow(dead_code)] pub fn has_dir(path: &str) -> bool { let dir = Path::new(&get_directory()).join(path); @@ -96,13 +90,19 @@ pub fn has_dir(path: &str) -> bool { true } +#[allow(dead_code)] 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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create directories: {}", + e + ); return String::new(); } return String::new(); @@ -110,7 +110,12 @@ pub fn load_file(path: &str, name: &str) -> String { if !file_path.exists() { if let Err(e) = File::create(&file_path) { - log!("[IMPORTANT] Couldn't create file: {}", e); + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create file: {}", + e + ); } return String::new(); } @@ -129,19 +134,27 @@ pub fn load_file_vec(path: &str, name: &str) -> Result, std::io::Error> std::fs::read(file_path) } +#[allow(dead_code)] 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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create directories: {}", + e + ); return; } } if let Err(e) = fs::write(&file_path, value) { log!( + 0, + PrintType::General, "[IMPORTANT] Couldn't write file {}: {}", file_path.display(), e @@ -149,6 +162,7 @@ pub fn save_file(path: &str, name: &str, value: &str) { } } +#[allow(dead_code)] pub fn get_children(path: &str) -> Vec { let dir = Path::new(&get_directory()).join(path); let mut children = Vec::new(); @@ -169,121 +183,3 @@ pub fn get_directory() -> String { .to_string_lossy() .to_string() } - -// Helper to download the zip file content to a file on disk -#[allow(dead_code)] -async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { - let response = reqwest::get(url).await?; - - // Check for successful response status - if !response.status().is_success() { - return Err(format!("Failed to download file: Status {}", response.status()).into()); - } - - let mut zip_file = tokio::fs::File::create(as_name).await?; - let body = response.bytes().await?; - zip_file.write_all(&body).await?; - - Ok(()) -} - -#[allow(dead_code, deprecated)] -fn extract_zip_contents_to_folder( - zip_path: &Path, - target_dir: &Path, -) -> Result<(), Box> { - let file = File::open(zip_path)?; - let mut archive = ZipArchive::new(file)?; - - let staging_dir = target_dir.with_extension("staging"); - - let _ = fs::remove_dir_all(&staging_dir); - fs::create_dir_all(&staging_dir)?; - - let mut first_item_name: Option = None; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - let entry_path = staging_dir.join(file.sanitized_name()); - - if i == 0 { - if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { - first_item_name = Some(file.sanitized_name()); - } - } - - if file.name().ends_with('/') { - fs::create_dir_all(&entry_path)?; - } else { - if let Some(parent) = entry_path.parent() { - fs::create_dir_all(parent)?; - } - let mut out_file = File::create(entry_path)?; - io::copy(&mut file, &mut out_file)?; - } - } - - if let Some(root_path) = first_item_name { - let root_dir = staging_dir.join(&root_path); - - if root_dir.is_dir() { - let root_contents_count = fs::read_dir(&staging_dir)?.count(); - - if root_contents_count == 1 - || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) - { - let _ = fs::remove_dir_all(target_dir); - fs::create_dir_all(target_dir)?; - - for entry in fs::read_dir(root_dir)? { - let entry = entry?; - let src = entry.path(); - let dest = target_dir.join(entry.file_name()); - - if let Err(_) = fs::rename(&src, &dest) { - if src.is_file() { - fs::copy(&src, &dest)?; - } else { - if entry.path().is_dir() { - fs::rename(&src, &dest)?; - } - } - } - } - - let _ = fs::remove_dir_all(&staging_dir); - return Ok(()); - } - } - } - - log!("Extracting directly (no single root folder detected)."); - let _ = fs::remove_dir_all(target_dir); - fs::rename(&staging_dir, target_dir)?; - - Ok(()) -} - -#[allow(dead_code)] -pub async fn download_and_extract_zip(url: &str, as_name: &str) { - let base_dir = PathBuf::from(get_directory()); - let zip_filename = format!("{}.zip", Uuid::new_v4()); - let zip_path = base_dir.join(&zip_filename); - let target_dir = base_dir.join(as_name); - - if let Err(e) = download_zip(url, &zip_path).await { - log!("Error downloading file: {}", e); - return; - } - - let zip_path_clone = zip_path.clone(); - let target_dir_clone = target_dir.clone(); - let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone); - if let Err(e) = extract_result { - log!("Panic during ZIP extraction: {}", e); - } - - if let Err(e) = tokio::fs::remove_file(&zip_path).await { - log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index d1a7764..9e9a085 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -9,13 +9,12 @@ use std::{ }; use ansi_term::Color; -use epsilon_core::{CommunicationValue, DataTypes, DataValue}; -use json::JsonValue; +use ttp_core::{CommunicationValue, DataTypes, DataValue}; static LOGGER: OnceLock> = OnceLock::new(); +#[allow(dead_code)] #[derive(Clone, Copy)] -#[allow(unused)] pub enum PrintType { Call, Client, @@ -96,12 +95,14 @@ fn fixed_box(content: &str, width: usize) -> String { } pub fn log_internal( - sender: Option, + sender: i64, kind: PrintType, prefix: &'static str, is_error: bool, message: String, ) { + let sender = if sender == 0 { None } else { Some(sender) }; + if let Some(tx) = LOGGER.get() { let _ = tx.send(LogMessage { timestamp_ms: SystemTime::now() @@ -116,102 +117,28 @@ pub fn log_internal( }); } } - #[macro_export] macro_rules! log { - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - "", - false, - format!($($arg)*) - ) - }; - - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) + ($sender: expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, "", false, format!($($arg)*)) }; } - #[macro_export] macro_rules! log_in { - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) - }; - - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - ">", - false, - format!($($arg)*) - ) + ($sender: expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, ">", false, format!($($arg)*)) }; } - #[macro_export] macro_rules! log_out { - - // sender + actor ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) - }; - - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - "<", - false, - format!($($arg)*) - ) + $crate::util::logger::log_internal($sender, $kind, "<", false, format!($($arg)*)) }; } - #[macro_export] macro_rules! log_err { - - // sender + actor ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) - }; - - // plain - ($($arg:tt)*) => { - $crate::util::logger::log_internal( - None, - $crate::util::logger::PrintType::General, - ">>", - true, - format!($($arg)*) - ) + $crate::util::logger::log_internal($sender, $kind, ">>", true, format!($($arg)*)) }; } @@ -224,7 +151,7 @@ pub fn log_cv_internal( let formatted = format_cv(cv); log_internal( - Some(cv.get_sender() as i64), + cv.get_sender() as i64, print_type.unwrap_or(PrintType::General), prefix, false, @@ -322,6 +249,7 @@ fn format_array(arr: Vec) -> String { parts.join(", ") } + #[macro_export] macro_rules! log_cv { ($kind:expr, $cv:expr) => { From 86cb583cf21a834bb99613df72e3eb7e3e22c24a Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 2 Apr 2026 22:28:07 +0200 Subject: [PATCH 101/220] Nuke repo --- .gitignore | 19 - Cargo.lock | 3910 ----------------- Cargo.toml | 37 - LICENSE | 16 - README.md | 1 - .../anonymous_client_connection.rs | 519 --- src/anonymous_clients/anonymous_manager.rs | 53 - src/anonymous_clients/mod.rs | 2 - src/calls/call_group.rs | 106 - src/calls/call_manager.rs | 97 - src/calls/call_util.rs | 151 - src/calls/caller.rs | 49 - src/calls/mod.rs | 4 - src/data/mod.rs | 1 - src/data/user.rs | 30 - src/main.rs | 51 - src/omega/mod.rs | 1 - src/omega/omega_connection.rs | 741 ---- src/omega/ping_pong_task.rs | 41 - src/rho/client_connection.rs | 583 --- src/rho/connection.rs | 358 -- src/rho/iota_connection.rs | 461 -- src/rho/mod.rs | 6 - src/rho/rho_connection.rs | 199 - src/rho/rho_manager.rs | 83 - src/rho/server.rs | 25 - src/util/crypto_helper.rs | 149 - src/util/crypto_util.rs | 178 - src/util/file_util.rs | 185 - src/util/logger.rs | 280 -- src/util/mod.rs | 4 - 31 files changed, 8340 deletions(-) delete mode 100644 .gitignore delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 src/anonymous_clients/anonymous_client_connection.rs delete mode 100644 src/anonymous_clients/anonymous_manager.rs delete mode 100644 src/anonymous_clients/mod.rs delete mode 100644 src/calls/call_group.rs delete mode 100644 src/calls/call_manager.rs delete mode 100644 src/calls/call_util.rs delete mode 100644 src/calls/caller.rs delete mode 100644 src/calls/mod.rs delete mode 100644 src/data/mod.rs delete mode 100644 src/data/user.rs delete mode 100644 src/main.rs delete mode 100644 src/omega/mod.rs delete mode 100644 src/omega/omega_connection.rs delete mode 100644 src/omega/ping_pong_task.rs delete mode 100644 src/rho/client_connection.rs delete mode 100755 src/rho/connection.rs delete mode 100755 src/rho/iota_connection.rs delete mode 100644 src/rho/mod.rs delete mode 100644 src/rho/rho_connection.rs delete mode 100644 src/rho/rho_manager.rs delete mode 100644 src/rho/server.rs delete mode 100755 src/util/crypto_helper.rs delete mode 100644 src/util/crypto_util.rs delete mode 100644 src/util/file_util.rs delete mode 100644 src/util/logger.rs delete mode 100644 src/util/mod.rs diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1baef92..0000000 --- a/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug -target - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb -*.env -# Generated by cargo mutants -# Contains mutation testing data -**/mutants.out*/W - - -# Added by cargo -/target -/logs diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 41cef35..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,3910 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "Omikron" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "ansi_term", - "base64 0.22.1", - "dashmap", - "dotenv", - "futures", - "hex", - "hkdf", - "livekit-api", - "livekit-protocol", - "log", - "once_cell", - "rand 0.8.5", - "rand_core 0.6.4", - "rustls", - "sha2", - "strum", - "strum_macros", - "thiserror 2.0.18", - "tokio", - "ttp-core", - "ttp-native", - "uuid", - "x448", -] - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -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 = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "asn1-rs" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-native-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" -dependencies = [ - "futures-util", - "native-tls", - "thiserror 1.0.69", - "url", -] - -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-tungstenite" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" -dependencies = [ - "async-native-tls", - "async-std", - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tungstenite 0.21.0", -] - -[[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.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" -dependencies = [ - "aws-lc-sys", - "untrusted 0.7.1", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[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 = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cc" -version = "1.2.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "num-traits", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -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-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto 0.2.9", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags", - "objc2", -] - -[[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 = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2", - "subtle", - "zeroize", -] - -[[package]] -name = "ed448-goldilocks" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" -dependencies = [ - "fiat-crypto 0.1.20", - "hex", - "subtle", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[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.2", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.1", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[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.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "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", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[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" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "httlib-huffman" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.4.0", -] - -[[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 1.4.0", - "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 = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http 1.4.0", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[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.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http 1.4.0", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2 0.6.3", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[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.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "10.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" -dependencies = [ - "base64 0.22.1", - "ed25519-dalek", - "getrandom 0.2.17", - "hmac", - "js-sys", - "p256", - "p384", - "rand 0.8.5", - "rsa", - "serde", - "serde_json", - "sha2", - "signature", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.184" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "livekit-api" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020c7eaab7cec16b69a058659f9acff32067e0e2d4f81e80639f3ffa29464b09" -dependencies = [ - "async-tungstenite", - "base64 0.21.7", - "http 1.4.0", - "jsonwebtoken", - "livekit-protocol", - "log", - "os_info", - "parking_lot", - "pbjson-types", - "prost", - "rand 0.9.2", - "reqwest", - "scopeguard", - "serde", - "serde_json", - "sha2", - "thiserror 1.0.69", - "tokio-tungstenite", - "url", -] - -[[package]] -name = "livekit-protocol" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c21d56b4dc2598b1296ca891abb225bf1c05ea007d6f43bc4fd48c31be64bf59" -dependencies = [ - "futures-util", - "livekit-runtime", - "parking_lot", - "pbjson", - "pbjson-types", - "prost", - "serde", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "livekit-runtime" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532e84c6cdc5fe774f2b5d9912597b5f3bea561927a48296d03e24549d21c3f6" -dependencies = [ - "tokio", - "tokio-stream", -] - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -dependencies = [ - "value-bag", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[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 = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "octets" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl" -version = "0.10.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" -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.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "os_info" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" -dependencies = [ - "android_system_properties", - "log", - "nix", - "objc2", - "objc2-foundation", - "objc2-ui-kit", - "serde", - "windows-sys 0.61.2", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "pbjson" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" -dependencies = [ - "base64 0.21.7", - "serde", -] - -[[package]] -name = "pbjson-build" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" -dependencies = [ - "heck 0.4.1", - "itertools 0.11.0", - "prost", - "prost-types", -] - -[[package]] -name = "pbjson-types" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" -dependencies = [ - "bytes", - "chrono", - "pbjson", - "pbjson-build", - "prost", - "prost-build", - "serde", -] - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[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 = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[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 = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" -dependencies = [ - "bytes", - "heck 0.5.0", - "itertools 0.12.1", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" -dependencies = [ - "prost", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.6.3", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.6.3", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[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.5", -] - -[[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.5", -] - -[[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.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rcgen" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" -dependencies = [ - "aws-lc-rs", - "rustls-pki-types", - "time", - "x509-parser 0.18.1", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "http 1.4.0", - "http-body", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "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 = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[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.17", - "libc", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted 0.9.0", -] - -[[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.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[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.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[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 = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn", -] - -[[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.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -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 = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2 0.6.3", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" -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-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite 0.20.1", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http 1.4.0", - "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.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "ttp-core" -version = "0.1.0" -source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" -dependencies = [ - "base64 0.22.1", - "byteorder", - "rand 0.8.5", - "strum", - "strum_macros", -] - -[[package]] -name = "ttp-native" -version = "0.1.0" -source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" -dependencies = [ - "quinn", - "rustls", - "rustls-native-certs", - "thiserror 2.0.18", - "tokio", - "ttp-core", - "wtransport", -] - -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 0.2.12", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.4.0", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[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.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -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.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" - -[[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 = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "web-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -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-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "wtransport" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" -dependencies = [ - "bytes", - "pem", - "quinn", - "rcgen", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "sha2", - "socket2 0.5.10", - "thiserror 2.0.18", - "time", - "tokio", - "tracing", - "url", - "wtransport-proto", - "x509-parser 0.17.0", -] - -[[package]] -name = "wtransport-proto" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" -dependencies = [ - "httlib-huffman", - "octets", - "thiserror 2.0.18", - "url", -] - -[[package]] -name = "x448" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" -dependencies = [ - "ed448-goldilocks", - "hex", - "rand_core 0.5.1", -] - -[[package]] -name = "x509-parser" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "aws-lc-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 3127b12..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "Omikron" -version = "0.1.0" -edition = "2024" - -[dependencies] -ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" } -ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" } - -ansi_term = "*" -uuid = { version = "*", features = ["v4"] } - -base64 = "0.22.1" -dashmap = "*" -futures = "*" -hex = "*" -once_cell = "1.21.3" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -rustls = { version = "0.23.37", default-features = false, features = [ - "std", - "tls12", - "aws-lc-rs", - "prefer-post-quantum", -] } -aes-gcm = "0.10.3" -sha2 = "0.10.9" -tokio = { version = "*", features = ["full"] } -x448 = { version = "*" } -log = "0.4" -dotenv = "0.15.0" -hkdf = "0.12.4" -strum = "0.28.0" -strum_macros = "0.28.0" -livekit-api = { version = "0.4.14", features = ["native-tls"] } -livekit-protocol = "0.7.1" -thiserror = "2.0.18" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d218eff..0000000 --- a/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -Copyright (c) [2025] [Methanium] -All rights reserved. - -This software is protected by copyright. Copying, editing, -distributing, publicly performing, or any other use of this software -or its components, in source or binary form, is strictly prohibited without the express -written permission of the copyright holder. - -FUTURE LICENSE ACCEPTANCE: -It is the copyright holder's intention to release this software in the future -under a license yet to be defined, which will, among other things, -allow private, non-commercial use. This statement does not constitute -a current license grant and does not alter the above -prohibition on use, copying, or modification. Until the formal -publication of such a future license, all rights remain -reserved. diff --git a/README.md b/README.md deleted file mode 100644 index 06c1cdb..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omikron) ⚠️ diff --git a/src/anonymous_clients/anonymous_client_connection.rs b/src/anonymous_clients/anonymous_client_connection.rs deleted file mode 100644 index 8c57490..0000000 --- a/src/anonymous_clients/anonymous_client_connection.rs +++ /dev/null @@ -1,519 +0,0 @@ -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Receiver, Sender}; -use uuid::Uuid; - -use crate::anonymous_clients::anonymous_manager::{self, generate_username}; -use crate::calls::call_manager; -use crate::omega::omega_connection::get_omega_connection; -use crate::rho::connection::GeneralConnection; -use crate::rho::rho_manager; -use crate::util::logger::PrintType; -use crate::{log_cv_in, log_cv_out, log_out}; - -pub struct AnonymousClientConnection { - user_id: u64, - - pub sender: Arc, - pub receiver: Arc, - pub ping: Arc>, - pub interested_users: Arc>>, - is_open: Arc>, - pub user_name: Arc>, - pub display_name: Arc>, - pub avatar: Arc>, -} - -impl AnonymousClientConnection { - pub async fn from_general(general: Arc, user_id: u64) -> Arc { - let username: String = generate_username(); - Arc::new(Self { - user_id: user_id, - - ping: Arc::new(RwLock::new(0)), - interested_users: Arc::new(RwLock::new(Vec::new())), - is_open: Arc::new(RwLock::new(true)), - sender: general.sender.clone(), - receiver: general.receiver.clone(), - user_name: Arc::new(RwLock::new(username.to_lowercase())), - display_name: Arc::new(RwLock::new(username)), - avatar: Arc::new(RwLock::new(String::new())), - }) - } - pub fn start(self: Arc) { - let self_clone = self.clone(); - tokio::spawn(async move { - while let Ok(cv) = self_clone.receiver.receive().await { - self_clone.clone().handle_message(cv).await; - } - self_clone.handle_close().await; - }); - } - - /// Get the user ID - pub fn get_user_id(&self) -> u64 { - self.user_id - } - - /// Get the user name - pub async fn get_user_name(&self) -> String { - self.user_name.read().await.clone() - } - - /// Get the display name - pub async fn get_display_name(&self) -> String { - self.display_name.read().await.clone() - } - - pub async fn set_display_name(&self, display: String) { - *self.display_name.write().await = display; - } - - /// Get the avatar - pub async fn get_avatar(&self) -> String { - self.avatar.read().await.clone() - } - - /// Send a CommunicationValue to the client - pub async fn send_message(self: Arc, cv: &CommunicationValue) { - if !*self.is_open.read().await { - log_out!( - self.user_id as i64, - PrintType::Client, - "Attempted to send message to a closed connection." - ); - return; - } - if !cv.is_type(CommunicationType::pong) { - log_cv_out!(PrintType::Client, &cv); - } - if let Err(e) = self.sender.send(&cv).await { - log_out!( - self.user_id as i64, - PrintType::Client, - "Send failed: {:?}", - e - ); - } - } - - /// Handle incoming message from client - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - tokio::spawn(async move { - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; - } - log_cv_in!(PrintType::Client, &cv); - - if cv.is_type(CommunicationType::identification) { - let call_id = - Uuid::parse_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")) - .unwrap_or(Uuid::new_v4()); - - let call = if let Some(call) = call_manager::get_call(call_id).await { - if call.is_anonymous().await { - call - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_not_authenticated, - ) - .await; - return; - } - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_not_authenticated, - ) - .await; - return; - }; - - let mut invited = Vec::new(); - for call_invitee in call.members.read().await.clone() { - let call_invitee_cv = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::get_user_data).add_data( - DataTypes::user_id, - DataValue::Number(call_invitee.user_id as i64), - ), - Some(Duration::from_secs(2)), - ) - .await - .unwrap(); - let mut json_invitee = Vec::new(); - let _ = json_invitee.push(( - DataTypes::user_id, - call_invitee_cv.get_data(DataTypes::user_id).clone(), - )); - let _ = json_invitee.push(( - DataTypes::username, - call_invitee_cv.get_data(DataTypes::username).clone(), - )); - let _ = json_invitee.push(( - DataTypes::display, - call_invitee_cv.get_data(DataTypes::display).clone(), - )); - let _ = json_invitee.push(( - DataTypes::avatar, - call_invitee_cv.get_data(DataTypes::avatar).clone(), - )); - - let _ = invited.push(DataValue::Container(json_invitee)); - } - - let token = call.create_anonymous_token(self.get_user_id()).await; - - let mut serialized = Vec::new(); - let _ = serialized.push((DataTypes::call_id, DataValue::Str(call_id.to_string()))); - let _ = - serialized.push((DataTypes::call_invited, DataValue::Array(invited.clone()))); - let _ = serialized.push((DataTypes::call_members, DataValue::Array(invited))); - let _ = serialized.push((DataTypes::call_token, DataValue::Str(token.unwrap()))); - self.clone() - .send_message( - &&CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(self.user_id as i64)) - .add_data( - DataTypes::username, - DataValue::Str(self.clone().get_user_name().await), - ) - .add_data( - DataTypes::display, - DataValue::Str(self.get_display_name().await), - ) - .add_data(DataTypes::avatar, DataValue::Str(self.get_avatar().await)) - .add_data(DataTypes::call_state, DataValue::Container(serialized)), - ) - .await; - } - - // 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::call_token) { - self.handle_get_call(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_disconnect_user) { - self.handle_call_disconnect_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_timeout_user) { - self.handle_call_timeout_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::change_user_data) { - if let Some(display_name) = cv.get_data(DataTypes::display).as_str() { - let _ = self.set_display_name(display_name.to_string()).await; - } - - return; - } - - if cv.is_type(CommunicationType::get_user_data) { - if let Some(anonymous) = { - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - anonymous_manager::get_anonymous_user(user_id as u64).await - } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { - anonymous_manager::get_anonymous_user_by_name(username.to_string()).await - } else { - None - } - } { - let response = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data( - DataTypes::username, - DataValue::Str(anonymous.get_user_name().await), - ) - .add_data( - DataTypes::user_id, - DataValue::Number(anonymous.user_id as i64), - ) - .add_data( - DataTypes::display, - DataValue::Str(anonymous.get_display_name().await), - ) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())) - .add_data( - DataTypes::avatar, - DataValue::Str(anonymous.get_avatar().await), - ); - - self.send_message(&response).await; - - return; - } - } - - if cv.is_type(CommunicationType::get_user_data) - || cv.is_type(CommunicationType::get_iota_data) - || cv.is_type(CommunicationType::delete_user) - { - self.handle_omega_forward(cv).await; - return; - } - }); - } - async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { - let client_for_closure = self.clone(); - tokio::spawn(async move { - let response_cv = get_omega_connection() - .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) - .await; - if let Ok(response_cv) = response_cv { - client_for_closure.send_message(&response_cv).await; - } - }); - } - - /// Handle ping message - async fn handle_ping(self: Arc, cv: CommunicationValue) { - // Update our ping if provided - if let DataValue::Number(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; - } - } - - // Send pong response - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); - - self.send_message(&response).await; - } - - /// Handle client status change - async fn handle_client_changed(self: Arc, _cv: CommunicationValue) { - /*let user_id = self.get_user_id().await; - if let Some(_status_str) = cv.get_data(DataTypes::user_state) { - let user_status = UserStatus::online; - }*/ - } - - /// Handle call invite - async fn handle_call_invite(self: Arc, cv: CommunicationValue) { - let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); - if receiver_id == 0 { - self.send_error_response(&cv.get_id(), CommunicationType::error_no_user_id) - .await; - return; - } - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(id_str) => match Uuid::parse_str(&id_str.to_string()) { - Ok(id) => id, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_call_id, - ) - .await; - return; - } - }, - _ => { - self.send_error_response(&cv.get_id(), CommunicationType::error_no_call_id) - .await; - return; - } - }; - - let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; - if !invited { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_call_id) - .await; - return; - } - - // Find target RhoConnection - let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { - Some(rho) => rho, - _ => { - self.send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - // Get sender user ID - let sender_id = self.get_user_id(); - - // Create and send call distribution message - let forward = CommunicationValue::new(CommunicationType::call_invite) - .with_receiver(receiver_id as u64) - .with_sender(sender_id) - .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) - .add_data( - DataTypes::receiver_id, - DataValue::Str(receiver_id.to_string()), - ) - .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); - - target_rho.message_to_client(forward).await; - - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } - - /// Handle get call request - async fn handle_get_call(self: Arc, cv: CommunicationValue) { - let user_id = self.get_user_id(); - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(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; - } - }, - _ => { - self.send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - if let Some(token) = call_manager::get_call_token(user_id, call_id).await { - let response = CommunicationValue::new(CommunicationType::call_token) - .with_id(cv.get_id()) - .with_receiver(user_id) - .add_data(DataTypes::call_token, DataValue::Str(token.to_string())); - self.send_message(&response).await; - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - } - async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id()) - .await - .unwrap() - .has_admin() - { - call.get_caller(user_id as u64) - .await - .unwrap() - .set_timeout(untill) - .await; - } - } - } - async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id()) - .await - .unwrap() - .has_admin() - { - call.remove_caller(user_id as u64).await; - } - } - } - - /// Send error response - async fn send_error_response(self: Arc, message_id: &u32, 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 is_open_guard = self.is_open.write().await; - if !*is_open_guard { - return; - } - *is_open_guard = false; - - let _ = self.sender.close(); - } - - #[allow(dead_code)] - /// Set interested users list - pub async fn set_interested_users(self: Arc, interested_ids: Vec) { - let mut interested_guard = self.interested_users.write().await; - *interested_guard = interested_ids; - } - #[allow(dead_code)] - pub async fn get_interested_users(self: Arc) -> Vec { - let interested_guard = self.interested_users.read().await; - interested_guard.clone() - } - - #[allow(dead_code)] - /// Check if interested in a user and send notification - pub async fn are_you_interested(self: Arc, user_id: i64) { - let interested_guard = self.clone().get_interested_users().await; - if interested_guard.contains(&user_id) { - let notification = CommunicationValue::new(CommunicationType::client_changed) - .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); - - self.send_message(¬ification).await; - } - } - - /// Handle connection close - pub async fn handle_close(&self) { - - // TODO delete temp user - } -} - -// Implement Clone to make it easier to work with Arc -impl Clone for AnonymousClientConnection { - fn clone(&self) -> Self { - Self { - sender: Arc::clone(&self.sender), - receiver: Arc::clone(&self.receiver), - user_id: self.user_id, - ping: Arc::clone(&self.ping), - interested_users: Arc::clone(&self.interested_users), - is_open: Arc::clone(&self.is_open), - user_name: Arc::clone(&self.user_name), - display_name: Arc::clone(&self.display_name), - avatar: Arc::clone(&self.avatar), - } - } -} diff --git a/src/anonymous_clients/anonymous_manager.rs b/src/anonymous_clients/anonymous_manager.rs deleted file mode 100644 index edfefde..0000000 --- a/src/anonymous_clients/anonymous_manager.rs +++ /dev/null @@ -1,53 +0,0 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::Rng; -use rand::seq::SliceRandom; -use std::sync::Arc; - -use crate::anonymous_clients::anonymous_client_connection::AnonymousClientConnection; - -static ANONYMOUS_USERS: Lazy>> = - Lazy::new(|| DashMap::new()); - -#[allow(dead_code)] -pub async fn add_anonymous_user(connection: Arc) { - ANONYMOUS_USERS.insert(connection.get_user_id(), connection); -} - -#[allow(dead_code)] -pub async fn remove_anonymous_user(user_id: u64) { - ANONYMOUS_USERS.remove(&user_id); -} - -pub async fn get_anonymous_user(user_id: u64) -> Option> { - ANONYMOUS_USERS.get(&user_id).map(|c| c.clone()) -} - -pub async fn get_anonymous_user_by_name( - username: String, -) -> Option> { - for user_conn in ANONYMOUS_USERS - .iter() - .map(|ref_multi| ref_multi.value().clone()) - { - if user_conn.get_user_name().await == username { - return Some(user_conn); - } - } - - return None; -} - -// TODO: implement check if taken -pub fn generate_username() -> String { - let adjectives = ["Swift", "Clever", "Brave", "Sneaky", "Fierce"]; - let nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Dragon"]; - - let mut rng = rand::thread_rng(); - let adj = adjectives.choose(&mut rng).unwrap(); - let noun = nouns.choose(&mut rng).unwrap(); - - let number: u16 = rng.gen_range(0..10000); - - format!("{}{}{}", adj, noun, number) -} diff --git a/src/anonymous_clients/mod.rs b/src/anonymous_clients/mod.rs deleted file mode 100644 index 3edd0b4..0000000 --- a/src/anonymous_clients/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod anonymous_client_connection; -pub mod anonymous_manager; diff --git a/src/calls/call_group.rs b/src/calls/call_group.rs deleted file mode 100644 index 91e85cf..0000000 --- a/src/calls/call_group.rs +++ /dev/null @@ -1,106 +0,0 @@ -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; - -use std::{env, sync::Arc, time::Duration}; -use tokio::sync::RwLock; -use uuid::Uuid; - -use crate::{ - calls::{call_util, caller::Caller}, - omega::omega_connection::get_omega_connection, -}; - -pub struct CallGroup { - pub call_id: Uuid, - pub members: RwLock>>, - pub show: RwLock, - pub anonymous_joining: RwLock, - pub short_link: RwLock>, -} - -impl CallGroup { - pub fn new(call_id: Uuid, user: Arc) -> Self { - CallGroup { - call_id, - members: RwLock::new(vec![user]), - show: RwLock::new(true), - anonymous_joining: RwLock::new(false), - short_link: RwLock::new(None), - } - } - - pub async fn get_caller(&self, user_id: u64) -> Option> { - self.members - .read() - .await - .iter() - .find(|caller| caller.user_id == user_id) - .cloned() - } - - pub async fn is_anonymous(&self) -> bool { - *self.anonymous_joining.read().await - } - - pub async fn set_anonymous_joining(&self, enable: bool) { - *self.anonymous_joining.write().await = enable; - - let _ = call_util::set_room_metadata( - self.call_id, - format!("{{\"anonymous_joining\": \"{}\"}}", enable), - ) - .await; - - if self.short_link.read().await.is_none() { - let long_link = format!( - "https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}", - self.call_id, - env::var("ID") - .unwrap_or("0".to_string()) - .parse::() - .unwrap_or(0), - ); - let response_cv = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::shorten_link) - .add_data(DataTypes::link, DataValue::Str(long_link)), - Some(Duration::from_secs(20)), - ) - .await; - if let Ok(response) = response_cv { - *self.short_link.write().await = Some( - response - .get_data(DataTypes::link) - .as_str() - .unwrap() - .to_string(), - ); - log::info!( - "Shortened link for call {} is {}", - self.call_id, - self.short_link.read().await.as_ref().unwrap() - ); - } - } - } - - pub async fn create_anonymous_token(&self, user_id: u64) -> Option { - if self.is_anonymous().await { - if let Ok(token) = call_util::create_token(user_id, self.call_id, false) { - return Some(token); - } - } - None - } - - pub async fn remove_caller(&self, user_id: u64) { - let _ = call_util::remove_participant(self.call_id, user_id).await; - self.members - .write() - .await - .retain(|caller| caller.user_id != user_id); - } - - pub async fn get_short_link(self: Arc) -> Option { - self.short_link.read().await.clone() - } -} diff --git a/src/calls/call_manager.rs b/src/calls/call_manager.rs deleted file mode 100644 index a17f347..0000000 --- a/src/calls/call_manager.rs +++ /dev/null @@ -1,97 +0,0 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use std::sync::Arc; -use uuid::Uuid; - -use crate::calls::{call_group::CallGroup, caller::Caller}; - -pub static CALL_GROUPS: Lazy>> = Lazy::new(|| DashMap::new()); -#[allow(dead_code)] -pub async fn get_call_invites(user_id: u64) -> Vec> { - let mut callers = Vec::new(); - for (_, cg) in CALL_GROUPS.clone().into_iter() { - let members = cg.members.read().await; - for member in members.iter() { - if member.user_id == user_id { - callers.push(member.clone()); - } - } - } - callers -} - -pub async fn get_call(call_id: Uuid) -> Option> { - if let Some(b) = CALL_GROUPS.get(&call_id) { - Some(b.clone()) - } else { - None - } -} - -pub async fn get_call_groups(user_id: u64) -> Vec> { - let mut call_groups = Vec::new(); - for (_, cg) in CALL_GROUPS.clone().into_iter() { - let is_member = { - let members = cg.members.read().await; - members.iter().any(|m| m.user_id == user_id) - }; - - if is_member { - call_groups.push(cg.clone()); - } - } - call_groups -} - -pub async fn get_call_token(user_id: u64, call_id: Uuid) -> Option { - if let Some(cg) = CALL_GROUPS.get(&call_id) { - let mut members = cg.members.write().await; - - if let Some(member) = members.iter().find(|m| m.user_id == user_id) { - return Some(member.create_token()); - } - - let new_caller = Arc::new(Caller::new(user_id, call_id, false)); - let token = new_caller.create_token(); - - members.push(new_caller); - - return Some(token); - } - - if let Some(cg) = CALL_GROUPS.get(&call_id) { - let cg_clone = cg.clone(); - - let mut members = cg_clone.members.write().await; - if let Some(member) = members.iter().find(|m| m.user_id == user_id) { - return Some(member.create_token()); - } - let new_caller = Arc::new(Caller::new(user_id, call_id, false)); - let token = new_caller.create_token(); - members.push(new_caller); - return Some(token); - } - - let caller = Arc::new(Caller::new(user_id, call_id, true)); - let call_group = CallGroup::new(call_id, caller.clone()); - - CALL_GROUPS.insert(call_id, Arc::new(call_group)); - - Some(caller.create_token()) -} - -pub async fn add_invite(call_id: Uuid, inviter_id: u64, invitee_id: u64) -> bool { - if let Some(cg) = CALL_GROUPS.get(&call_id) { - let mut members = cg.members.write().await; - - let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id); - - if is_inviter_member { - if !members.iter().any(|m| m.user_id == invitee_id) { - members.push(Arc::new(Caller::new(invitee_id, call_id, false))); - } - return true; - } - } - false -} diff --git a/src/calls/call_util.rs b/src/calls/call_util.rs deleted file mode 100644 index 57df005..0000000 --- a/src/calls/call_util.rs +++ /dev/null @@ -1,151 +0,0 @@ -use livekit_api::{ - access_token::{self}, - services::room::RoomClient, -}; -use livekit_protocol::Room; -use std::env; -use std::str::FromStr; -use std::time::Duration; -use uuid::Uuid; - -use crate::{calls::call_manager::CALL_GROUPS, log, log_err, util::logger::PrintType}; - -pub fn get_livekit() -> Result<(String, String, String), ()> { - let hostname = match env::var("LIVEKI_HOSTNAME") { - Ok(secret) => secret, - Err(_) => { - log_err!(0, PrintType::General, "LIVEKI_HOSTNAME not set!"); - return Err(()); - } - }; - let api_key = match env::var("LIVEKIT_API_KEY") { - Ok(key) => key, - Err(_) => { - log_err!(0, PrintType::General, "LIVEKIT_API_KEY not set!"); - return Err(()); - } - }; - let api_secret = match env::var("LIVEKIT_API_SECRET") { - Ok(secret) => secret, - Err(_) => { - log_err!(0, PrintType::General, "LIVEKIT_API_SECRET not set!"); - return Err(()); - } - }; - Ok((hostname, api_key, api_secret)) -} - -pub fn create_token(user_id: u64, call_id: Uuid, has_admin: bool) -> Result { - let (_, api_key, api_secret) = get_livekit()?; - - let token = access_token::AccessToken::with_api_key(&api_key, &api_secret) - .with_identity(&user_id.to_string()) - .with_grants(access_token::VideoGrants { - room_join: true, - room_admin: has_admin, - room: call_id.to_string(), - ..Default::default() - }) - .with_metadata(&format!("{{\"isAdmin\":{}}}", has_admin)) - .to_jwt(); - if let Ok(token) = token { - Ok(token) - } else { - Err(()) - } -} -#[allow(dead_code)] -pub async fn get_room(call_id: Uuid) -> Result<(RoomClient, Room), ()> { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - let rooms = room_service.list_rooms(Vec::new()).await; - if let Ok(rooms) = rooms { - for room in rooms { - if room.name == call_id.to_string() { - return Ok((room_service, room)); - } - } - } - } - return Err(()); -} - -pub async fn remove_participant(call_id: Uuid, user_id: u64) -> Result<(), ()> { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - if let Ok(_) = room_service - .remove_participant(&call_id.to_string(), &user_id.to_string()) - .await - { - return Ok(()); - } - } - return Err(()); -} - -#[allow(dead_code)] -pub async fn get_room_metadata(call_id: Uuid) -> Result { - if let Ok((_, room)) = get_room(call_id).await { - Ok(room.metadata) - } else { - Err(()) - } -} - -pub async fn set_room_metadata(call_id: Uuid, metadata: String) -> Result<(), ()> { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - if let Ok(_) = room_service - .update_room_metadata(&call_id.to_string(), &metadata) - .await - { - return Ok(()); - } - } - return Err(()); -} - -pub fn garbage_collect_calls() { - tokio::spawn(async move { - loop { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - clean_calls(room_service).await; - } - tokio::time::sleep(Duration::from_secs(2)).await; - } - }); -} -pub async fn clean_calls(room_service: RoomClient) { - let rooms = room_service.list_rooms(Vec::new()).await.unwrap(); - let mut call_ids: Vec = Vec::new(); - let mut no_users: Vec = Vec::new(); - for room in rooms { - if let Ok(id) = Uuid::from_str(&room.name) { - if room.num_participants == 0 { - no_users.push(id); - } - call_ids.push(id); - } - } - let size_pre = CALL_GROUPS.len(); - for (id, _) in CALL_GROUPS.clone().into_iter() { - if !call_ids.contains(&id) { - CALL_GROUPS.remove(&id); - } - } - for (_, cg) in CALL_GROUPS.clone().into_iter() { - *cg.show.write().await = !no_users.contains(&cg.call_id); - } - - let size_post = CALL_GROUPS.len(); - if size_pre - size_post != 0 { - log!( - 0, - PrintType::Call, - "Cleaned {} calls, {} remaining", - size_pre - size_post, - size_post - ); - } -} diff --git a/src/calls/caller.rs b/src/calls/caller.rs deleted file mode 100644 index 74e0c3b..0000000 --- a/src/calls/caller.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - -use tokio::sync::RwLock; -use uuid::Uuid; - -use crate::calls::call_util; - -pub struct Caller { - pub user_id: u64, - pub call_id: Uuid, - pub has_admin: bool, - pub timeout: RwLock, -} - -impl Caller { - pub fn new(user_id: u64, call_id: Uuid, has_admin: bool) -> Self { - Caller { - user_id, - call_id, - has_admin, - timeout: RwLock::new(0), - } - } - #[allow(dead_code)] - pub fn set_admin(&mut self, has_admin: bool) { - self.has_admin = has_admin; - } - pub fn has_admin(&self) -> bool { - self.has_admin - } - #[allow(dead_code)] - pub async fn is_timeouted(&self) -> bool { - *self.timeout.read().await - > SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as i64 - } - pub async fn set_timeout(&self, timeout: i64) { - *self.timeout.write().await = timeout; - } - pub fn create_token(&self) -> String { - if let Ok(token) = call_util::create_token(self.user_id, self.call_id, self.has_admin()) { - token - } else { - String::new() - } - } -} diff --git a/src/calls/mod.rs b/src/calls/mod.rs deleted file mode 100644 index 8413354..0000000 --- a/src/calls/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod call_group; -pub mod call_manager; -pub mod call_util; -pub mod caller; diff --git a/src/data/mod.rs b/src/data/mod.rs deleted file mode 100644 index 22d12a3..0000000 --- a/src/data/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod user; diff --git a/src/data/user.rs b/src/data/user.rs deleted file mode 100644 index a175e6d..0000000 --- a/src/data/user.rs +++ /dev/null @@ -1,30 +0,0 @@ -use strum::IntoEnumIterator; -use strum_macros::EnumIter; - -#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] -#[allow(unused, non_camel_case_types)] -pub enum UserStatus { - user_offline, - user_online, - user_dnd, - user_idle, - user_wc, - user_borked, - iota_offline, - iota_online, - iota_borked, -} -#[allow(unused)] -impl UserStatus { - pub fn to_string(&self) -> String { - format!("{:?}", self) - } - pub fn from_str(s: &str) -> Option { - for sel in UserStatus::iter() { - if &sel.to_string() == s { - return Some(sel); - } - } - None - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index fa511ea..0000000 --- a/src/main.rs +++ /dev/null @@ -1,51 +0,0 @@ -mod anonymous_clients; -mod calls; -mod data; -mod omega; -mod rho; -mod util; - -use std::env; - -use dotenv::dotenv; -use once_cell::sync::Lazy; -use rustls::crypto::aws_lc_rs::default_provider; - -use crate::{ - calls::call_util::garbage_collect_calls, - omega::omega_connection::get_omega_connection, - rho::server::start, - util::{ - crypto_helper::{load_public_key, load_secret_key}, - logger::startup, - }, -}; - -static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); -pub fn get_private_key() -> x448::Secret { - load_secret_key(&*PRIVATE_KEY).unwrap() -} -static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); -pub fn get_public_key() -> x448::PublicKey { - load_public_key(&*PUBLIC_KEY).unwrap() -} - -#[tokio::main] -async fn main() { - if let Err(_) = default_provider().install_default() { - println!("Error loading Provider"); - return; - } - dotenv().ok(); - startup(); - - get_omega_connection(); - tokio::spawn(async move { - if let Err(e) = start(959).await { - log_err!(0, util::logger::PrintType::General, "{}", e); - } - }); - garbage_collect_calls(); - - tokio::signal::ctrl_c().await.unwrap(); -} diff --git a/src/omega/mod.rs b/src/omega/mod.rs deleted file mode 100644 index cbe77d9..0000000 --- a/src/omega/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod omega_connection; diff --git a/src/omega/omega_connection.rs b/src/omega/omega_connection.rs deleted file mode 100644 index ab84eb2..0000000 --- a/src/omega/omega_connection.rs +++ /dev/null @@ -1,741 +0,0 @@ -use crate::{ - data::user::UserStatus, - get_private_key, log, log_cv_in, log_cv_out, log_err, log_in, - rho::rho_manager::{self, RHO_CONNECTIONS, connection_count}, - util::{ - crypto_helper::{decrypt_b64, secret_key_to_base64}, - file_util::load_file_vec, - logger::PrintType, - }, -}; -use dashmap::DashMap; -use once_cell::sync::Lazy; -use std::{collections::HashMap, env, sync::Arc, time::Duration}; -use tokio::{ - sync::{Mutex, RwLock, mpsc, watch}, - task::JoinHandle, - time::{Instant, sleep}, -}; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; -use ttp_native::{Receiver, Sender}; -use uuid::Uuid; - -// ============================================================================ -// Configuration -// ============================================================================ - -const OMEGA_HOST_DEFAULT: &str = "methanium.net"; -const OMEGA_PORT_DEFAULT: u16 = 9187; -const RECONNECT_DELAY: Duration = Duration::from_secs(5); -const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); -const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); -const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); -const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); -const TASK_MAX_AGE: Duration = Duration::from_secs(60); - -// ============================================================================ -// Waiting Task System -// ============================================================================ - -pub struct WaitingTask { - pub task: Box, CommunicationValue) -> bool + Send + Sync>, - pub inserted_at: Instant, -} - -pub static WAITING_TASKS: Lazy> = Lazy::new(DashMap::new); - -pub fn start_task_cleanup_loop() { - tokio::spawn(async { - loop { - sleep(TASK_CLEANUP_INTERVAL).await; - WAITING_TASKS.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE); - } - }); -} - -// ============================================================================ -// Connection State -// ============================================================================ - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum ConnectionState { - Disconnected, - Connecting, - Connected { identified: bool }, -} - -#[allow(unused_variables)] -impl ConnectionState { - pub fn is_connected(&self) -> bool { - match self { - ConnectionState::Connected { identified } => true, - _ => false, - } - } - - #[allow(dead_code)] - pub fn is_identified(&self) -> bool { - match self { - ConnectionState::Connected { identified: true } => true, - _ => false, - } - } -} - -// ============================================================================ -// Omega Connection (Client-side with auto-reconnect) -// ============================================================================ -#[allow(dead_code)] -pub struct OmegaConnection { - state: Arc>, - sender: Arc>>>, - connection_loop_handle: Arc>>>, - host: String, - port: u16, - server_cert: Vec, - last_ping: Arc>, - heartbeat_handle: Arc>>>, - message_send_times: Arc>>, - pub connection_id: Uuid, - shutdown_tx: Arc>>>, - // Track if we should reconnect on close - reconnect_on_close: Arc>, -} - -impl OmegaConnection { - pub fn new() -> Self { - Self::with_host(OMEGA_HOST_DEFAULT, OMEGA_PORT_DEFAULT) - } - - pub fn with_host(host: &str, port: u16) -> Self { - // Load server certificate from default location - let server_cert = - load_file_vec("certs", "cert.pem").expect("Failed to load server certificate"); - - Self::with_host_and_cert(host, port, server_cert) - } - - // New constructor that accepts certificate directly - pub fn with_host_and_cert(host: &str, port: u16, server_cert: Vec) -> Self { - let (shutdown_tx, _) = watch::channel(false); - - OmegaConnection { - state: Arc::new(RwLock::new(ConnectionState::Disconnected)), - sender: Arc::new(RwLock::new(None)), - connection_loop_handle: Arc::new(Mutex::new(None)), - host: host.to_string(), - port, - server_cert, - last_ping: Arc::new(Mutex::new(-1)), - heartbeat_handle: Arc::new(Mutex::new(None)), - message_send_times: Arc::new(Mutex::new(HashMap::new())), - connection_id: Uuid::new_v4(), - shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), - reconnect_on_close: Arc::new(RwLock::new(true)), - } - } - - // ------------------------------------------------------------------------- - // Connection Management - // ------------------------------------------------------------------------- - - pub async fn start(self: Arc) { - // Cancel any existing connection loop - if let Some(handle) = self.connection_loop_handle.lock().await.take() { - handle.abort(); - } - - // Set reconnect flag - *self.reconnect_on_close.write().await = true; - - let self_clone = self.clone(); - let handle = tokio::spawn(async move { - self_clone.connection_loop().await; - }); - - *self.connection_loop_handle.lock().await = Some(handle); - } - - #[allow(dead_code)] - pub async fn stop(&self) { - // Disable reconnection - *self.reconnect_on_close.write().await = false; - - if let Some(tx) = self.shutdown_tx.lock().await.take() { - let _ = tx.send(true); - } - - if let Some(handle) = self.connection_loop_handle.lock().await.take() { - handle.abort(); - } - - if let Some(handle) = self.heartbeat_handle.lock().await.take() { - handle.abort(); - } - - // Close sender if connected - if let Some(sender) = self.sender.read().await.as_ref() { - sender.close(); - } - - *self.state.write().await = ConnectionState::Disconnected; - *self.sender.write().await = None; - } - - async fn connection_loop(self: Arc) { - let mut reconnect_delay = RECONNECT_DELAY; - let shutdown_rx = self.shutdown_tx.lock().await.as_ref().unwrap().subscribe(); - let mut shutdown_rx = shutdown_rx; - - loop { - if *shutdown_rx.borrow() { - log_in!(0, PrintType::Omega, "Connection loop shutting down"); - break; - } - - // Check if reconnection is enabled - if !*self.reconnect_on_close.read().await { - log_in!(0, PrintType::Omega, "Reconnection disabled, exiting loop"); - break; - } - - match self.clone().connect_once().await { - Ok(()) => { - // Connection closed gracefully, check if we should reconnect - if *self.reconnect_on_close.read().await { - log_err!( - 0, - PrintType::Omega, - "Connection lost, reconnecting in {:?}...", - reconnect_delay - ); - } else { - log_in!(0, PrintType::Omega, "Connection closed, not reconnecting"); - break; - } - } - Err(e) => { - log_err!( - 0, - PrintType::Omega, - "Connection failed: {}, retrying in {:?}...", - e, - reconnect_delay - ); - } - } - - tokio::select! { - _ = sleep(reconnect_delay) => {} - _ = shutdown_rx.changed() => { - if *shutdown_rx.borrow() { - break; - } - } - } - - reconnect_delay = std::cmp::min(reconnect_delay * 2, MAX_RECONNECT_DELAY); - } - } - - async fn connect_once(self: Arc) -> Result<(), String> { - *self.state.write().await = ConnectionState::Connecting; - - let addr_str = format!("https://{}:{}", self.host, self.port); - - let (sender, mut receiver) = ttp_native::client::connect(&addr_str, None) - .await - .map_err(|e| format!("Connection failed: {}", e))?; - - log_in!( - 0, - PrintType::Omega, - "QUIC connection established to {}", - addr_str - ); - - // Store sender - let sender_arc = Arc::new(sender); - *self.sender.write().await = Some(sender_arc.clone()); - *self.state.write().await = ConnectionState::Connected { identified: false }; - - // Get handle for close monitoring - let sender_handle = sender_arc.handle().clone(); - - // Start read loop - let read_self = self.clone(); - let read_handle = tokio::spawn(async move { - read_self.read_loop(&mut receiver, sender_handle).await; - }); - - // Send identification - self.send_identification().await; - - // Start heartbeat - let heartbeat_self = self.clone(); - let heartbeat_handle = tokio::spawn(async move { - heartbeat_self.heartbeat_loop().await; - }); - *self.heartbeat_handle.lock().await = Some(heartbeat_handle); - - // Wait for read loop to complete (connection closed) - let result = read_handle.await; - - // Cleanup - *self.sender.write().await = None; - *self.state.write().await = ConnectionState::Disconnected; - - if let Some(handle) = self.heartbeat_handle.lock().await.take() { - handle.abort(); - } - - match result { - Ok(()) => { - // Check if we should reconnect - if *self.reconnect_on_close.read().await { - Err("Connection closed, will reconnect".to_string()) - } else { - Ok(()) - } - } - Err(e) => Err(format!("Read loop error: {}", e)), - } - } - - // ------------------------------------------------------------------------- - // Identification Handshake - // ------------------------------------------------------------------------- - - async fn send_identification(&self) { - let id = rand_u32(); - - let omikron_id = env::var("ID") - .unwrap_or("0".to_string()) - .parse::() - .unwrap_or(0); - - let identify_msg = CommunicationValue::new(CommunicationType::identification) - .with_id(id) - .add_data(DataTypes::omikron_id, DataValue::Number(omikron_id)); - - WAITING_TASKS.insert( - id, - WaitingTask { - task: Box::new(|selfc, cv| { - if cv.is_type(CommunicationType::error_not_found) { - log_err!( - 0, - PrintType::Omega, - "Identification failed: Omikron ID not found" - ); - return false; - } - if !cv.is_type(CommunicationType::challenge) { - return false; - } - - tokio::spawn(async move { - if let Err(e) = selfc.handle_challenge(cv).await { - log_err!(0, PrintType::Omega, "Challenge handling failed: {}", e); - } - }); - true - }), - inserted_at: Instant::now(), - }, - ); - - self.send_message(&identify_msg).await; - } - - async fn handle_challenge(&self, cv: CommunicationValue) -> Result<(), String> { - let challenge = cv - .get_data(DataTypes::challenge) - .as_str() - .ok_or("Challenge not found")?; - - let server_pub_key = cv - .get_data(DataTypes::public_key) - .as_str() - .ok_or("Public key not found")?; - - let decrypted_challenge = decrypt_b64( - &secret_key_to_base64(&get_private_key()), - server_pub_key, - challenge, - ) - .map_err(|e| format!("Decryption failed: {:?}", e))?; - - let response_msg = CommunicationValue::new(CommunicationType::challenge_response) - .with_id(cv.get_id()) - .add_data(DataTypes::challenge, DataValue::Str(decrypted_challenge)); - - let response_id = response_msg.get_id(); - - WAITING_TASKS.insert( - response_id, - WaitingTask { - task: Box::new(|selfc, final_cv| { - if !final_cv.is_type(CommunicationType::identification_response) { - log_err!(0, PrintType::Omega, "Expected identification_response"); - return false; - } - - let accepted = final_cv - .get_data(DataTypes::accepted) - .as_bool() - .unwrap_or(false); - - if !accepted { - log_err!(0, PrintType::Omega, "Omega did not accept identification"); - return false; - } - - tokio::spawn(async move { - let mut state = selfc.state.write().await; - if let ConnectionState::Connected { identified: _ } = *state { - *state = ConnectionState::Connected { identified: true }; - } - drop(state); - - selfc.sync_client_iota_status().await; - }); - - log!(0, PrintType::Omega, "Successfully identified with Omega"); - true - }), - inserted_at: Instant::now(), - }, - ); - - self.send_message(&response_msg).await; - Ok(()) - } - - async fn sync_client_iota_status(self: Arc) { - let mut connected_iota_ids: Vec = Vec::new(); - let mut connected_user_ids: Vec = Vec::new(); - - let rho_connections_reader = RHO_CONNECTIONS.read().await; - - for iota_id in rho_connections_reader.keys() { - connected_iota_ids.push(DataValue::Number(*iota_id)); - } - - for rho in rho_connections_reader.values() { - for client_conn in rho.get_client_connections().await { - connected_user_ids.push(DataValue::Number(client_conn.get_user_id().await as i64)); - } - } - - drop(rho_connections_reader); - - let sync_msg = CommunicationValue::new(CommunicationType::sync_client_iota_status) - .add_data(DataTypes::iota_ids, DataValue::Array(connected_iota_ids)) - .add_data(DataTypes::user_ids, DataValue::Array(connected_user_ids)) - .add_data( - DataTypes::rho_connections, - DataValue::Number(connection_count().await as i64), - ); - - self.send_message(&sync_msg).await; - } - - // ------------------------------------------------------------------------- - // Read Loop & Heartbeat - // ------------------------------------------------------------------------- - - async fn read_loop( - self: Arc, - receiver: &mut Receiver, - sender_handle: Arc, - ) { - // Monitor both receiver and sender handle for close - let mut close_rx = sender_handle.subscribe_close(); - - loop { - tokio::select! { - result = receiver.receive() => { - match result { - Ok(cv) => { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_in!(PrintType::Omega, &cv); - } - - if cv.is_type(CommunicationType::pong) || cv.is_type(CommunicationType::ping) { - self.handle_pong(&cv).await; - continue; - } - - let msg_id = cv.get_id(); - if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { - if (task.task)(self.clone(), cv.clone()) { - continue; - } - } - - if cv.is_type(CommunicationType::iota_user_data) { - if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { - let mut user_ids: Vec = Vec::new(); - for value in users { - if let DataValue::Number(user_id) = value { - user_ids.push(*user_id as u64); - } - } - let connections = crate::rho::rho_manager::RHO_CONNECTIONS.read().await; - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - if let Some(rho) = connections.get(&iota_id) { - rho.get_iota_connection().set_user_ids(user_ids).await; - } - } else { - for rho in connections.values() { - rho.get_iota_connection().set_user_ids(user_ids.clone()).await; - } - } - } - } - } - Err(e) => { - log_err!(0, PrintType::Omega, "Receive error: {}", e); - break; - } - } - } - _ = close_rx.changed() => { - // Connection was closed by either side - if let Some(reason) = close_rx.borrow().clone() { - log_err!(0, PrintType::Omega, "Connection closed: {:?}", reason); - } else { - log_in!(0, PrintType::Omega, "Connection closed cleanly"); - } - break; - } - } - } - } - - async fn heartbeat_loop(self: Arc) { - loop { - sleep(HEARTBEAT_INTERVAL).await; - - // Check if still connected - if !self.state.read().await.is_connected() { - break; - } - - // Check if sender is closed - if let Some(sender) = self.sender.read().await.as_ref() { - if sender.is_closed() { - log_err!(0, PrintType::Omega, "Sender closed, stopping heartbeat"); - break; - } - } else { - break; - } - - self.send_ping().await; - } - } - - async fn send_ping(&self) { - let ping = CommunicationValue::new(CommunicationType::ping).add_data( - DataTypes::send_time, - DataValue::Number( - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as i64, - ), - ); - self.send_message(&ping).await; - } - - async fn handle_pong(&self, cv: &CommunicationValue) { - let timestamp = cv - .get_data(DataTypes::send_time) - .as_number() - .unwrap_or_else(|| { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as i64 - }); - - *self.last_ping.lock().await = timestamp; - } - - // ------------------------------------------------------------------------- - // Public API - // ------------------------------------------------------------------------- - - pub async fn send_message(&self, cv: &CommunicationValue) { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_out!(PrintType::Omega, &cv); - } - - let sender_guard = self.sender.read().await; - if let Some(sender) = sender_guard.as_ref() { - // Check if closed before sending - if sender.is_closed() { - log_err!(0, PrintType::Omega, "Cannot send: connection closed"); - drop(sender_guard); - // Trigger reconnection by closing the connection state - if let Some(sender) = self.sender.write().await.take() { - sender.close(); - } - return; - } - - let sender_clone = Arc::clone(sender); - drop(sender_guard); - - if let Err(e) = sender_clone.send(cv).await { - log_err!(0, PrintType::Omega, "Send failed: {}", e); - } - } else { - log_err!(0, PrintType::Omega, "Cannot send: not connected"); - } - } - - pub async fn await_connection(&self, timeout_duration: Option) -> Result<(), String> { - if self.state.read().await.is_connected() { - return Ok(()); - } - - let timeout = timeout_duration.unwrap_or(CONNECTION_TIMEOUT); - let start = Instant::now(); - - loop { - if self.state.read().await.is_connected() { - return Ok(()); - } - - if start.elapsed() >= timeout { - return Err(format!( - "Connection not established within {} seconds", - timeout.as_secs() - )); - } - - sleep(Duration::from_millis(100)).await; - } - } - - pub async fn await_response( - &self, - cv: &CommunicationValue, - timeout_duration: Option, - ) -> Result { - self.await_connection(timeout_duration).await?; - - let (tx, mut rx) = mpsc::channel(1); - let msg_id = cv.get_id(); - - WAITING_TASKS.insert( - msg_id, - WaitingTask { - task: Box::new(move |_, response_cv| { - let inner_tx = tx.clone(); - tokio::spawn(async move { - let _ = inner_tx.send(response_cv).await; - }); - true - }), - inserted_at: Instant::now(), - }, - ); - - self.send_message(cv).await; - - let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); - - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response_cv)) => Ok(response_cv), - Ok(_) => Err("Channel closed".to_string()), - Err(_) => { - WAITING_TASKS.remove(&msg_id); - Err("Request timed out".to_string()) - } - } - } - - #[allow(dead_code)] - pub async fn is_connected(&self) -> bool { - self.state.read().await.is_connected() - } - #[allow(dead_code)] - pub async fn is_identified(&self) -> bool { - self.state.read().await.is_identified() - } - - #[allow(dead_code)] - pub async fn close_iota(iota_id: i64) { - let cv = CommunicationValue::new(CommunicationType::iota_disconnected) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); - OMEGA_CONNECTION.send_message(&cv).await; - } - - pub async fn client_changed(_iota_id: i64, user_id: i64, state: UserStatus) { - let msg_type = match state { - UserStatus::iota_offline => CommunicationType::user_disconnected, - UserStatus::user_offline => CommunicationType::user_disconnected, - _ => CommunicationType::user_connected, - }; - - let cv = CommunicationValue::new(msg_type) - .add_data(DataTypes::user_id, DataValue::Number(user_id)); - OMEGA_CONNECTION.send_message(&cv).await; - } - - pub async fn user_states(user_id: i64, user_ids: Vec) { - let user_ids = user_ids.iter().map(|v| DataValue::Number(*v)).collect(); - - let cv = CommunicationValue::new(CommunicationType::get_states) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); - let msg_id = cv.get_id(); - - WAITING_TASKS.insert( - msg_id, - WaitingTask { - task: Box::new( - move |_: Arc, response: CommunicationValue| { - tokio::spawn(async move { - 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 - }, - ), - inserted_at: Instant::now(), - }, - ); - - OMEGA_CONNECTION.send_message(&cv).await; - } -} - -// ============================================================================ -// Global Instance -// ============================================================================ - -static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { - let conn = Arc::new(OmegaConnection::new()); - - // Start the connection manager immediately - let conn_clone = conn.clone(); - tokio::spawn(async move { - conn_clone.start().await; - }); - - start_task_cleanup_loop(); - - conn -}); - -pub fn get_omega_connection() -> Arc { - OMEGA_CONNECTION.clone() -} diff --git a/src/omega/ping_pong_task.rs b/src/omega/ping_pong_task.rs deleted file mode 100644 index c7fc0b9..0000000 --- a/src/omega/ping_pong_task.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::time::Duration; -use tokio::time::Instant; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; - -use crate::omega::omega_connection::OmegaConnection; - -const PING_TIMEOUT: Duration = Duration::from_secs(30); - -impl OmegaConnection { - pub async fn send_ping(&self) { - let id = rand_u32(); - let send_time = Instant::now(); - - let mut message_send_times = self.message_send_times.lock().await; - message_send_times.retain(|_uuid, time| time.elapsed() < PING_TIMEOUT); - message_send_times.insert(id as i64, send_time); - - self.send_ping_message(id).await; - } - - pub async fn send_ping_message(&self, id: u32) { - let ping_message = CommunicationValue::new(CommunicationType::ping) - .with_id(id) - .add_data( - DataTypes::last_ping, - DataValue::Number(self.last_ping.lock().await.unwrap()), - ); - - self.send_message(&ping_message).await; - } - - /// Handles incoming pong and calculates latency - pub async fn handle_pong(&self, cv: &CommunicationValue, _log: bool) { - let id = cv.get_id(); - let mut message_send_times = self.message_send_times.lock().await; - if let Some(send_time) = message_send_times.remove(&(id as i64)) { - let ping = Instant::now().duration_since(send_time).as_millis() as i64; - *self.last_ping.lock().await = ping; - } - } -} diff --git a/src/rho/client_connection.rs b/src/rho/client_connection.rs deleted file mode 100644 index ebaabf2..0000000 --- a/src/rho/client_connection.rs +++ /dev/null @@ -1,583 +0,0 @@ -use crate::anonymous_clients::anonymous_manager; -use crate::calls::{call_manager, call_util}; -use crate::omega::omega_connection::get_omega_connection; -use crate::rho::connection::GeneralConnection; -use crate::rho::{rho_connection::RhoConnection, rho_manager}; -use crate::util::logger::PrintType; -use crate::{data::user::UserStatus, omega::omega_connection::OmegaConnection}; -use crate::{log_cv_in, log_cv_out, log_err, log_in, log_out}; -use std::str::FromStr; -use std::sync::Arc; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Receiver, Sender}; -use uuid::Uuid; - -pub struct ClientConnection { - pub user_id: u64, - - pub sender: Arc, - pub receiver: Arc, - pub ping: Arc>, - pub_key: Arc>>>, - pub rho_connection: Arc>>>, - pub interested_users: Arc>>, - is_open: Arc>, -} - -impl ClientConnection { - pub async fn from_general(general: Arc, user_id: u64) -> Arc { - Arc::new(Self { - ping: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - rho_connection: general.rho_connection.clone(), - interested_users: Arc::new(RwLock::new(Vec::new())), - is_open: Arc::new(RwLock::new(true)), - sender: general.sender.clone(), - receiver: general.receiver.clone(), - user_id: user_id, - }) - } - pub fn start(self: Arc) { - let self_clone = self.clone(); - tokio::spawn(async move { - while let Ok(cv) = self_clone.receiver.receive().await { - self_clone.clone().handle_message(cv).await; - } - self_clone.handle_close().await; - }); - - let self_clone2 = self.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(50)).await; - if self_clone2.get_rho_connection().await.is_none() { - self_clone2 - .send_error_response(0, CommunicationType::error) - .await; - } - }); - } - - /// Get the user ID - pub async fn get_user_id(&self) -> u64 { - self.user_id - } - - /// Get current ping - pub async fn get_ping(&self) -> i64 { - *self.ping.read().await - } - - /// Get RhoConnection if available - pub async fn get_rho_connection(&self) -> Option> { - self.rho_connection.read().await.clone() - } - - /// Send a CommunicationValue to the client - pub async fn send_message(self: Arc, cv: &CommunicationValue) { - if !*self.is_open.read().await { - log_out!( - self.user_id as i64, - PrintType::Client, - "Attempted to send message to a closed connection." - ); - return; - } - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_out!(PrintType::Client, &cv); - } - let _ = self.sender.send(&cv).await; - } - - /// Handle incoming message from client - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - tokio::spawn(async move { - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; - } - log_cv_in!(PrintType::Client, cv); - - // 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::call_token) { - self.handle_get_call(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_disconnect_user) { - self.handle_call_disconnect_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_timeout_user) { - self.handle_call_timeout_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_set_anonymous_joining) { - self.handle_call_set_anonymous_joining(cv).await; - return; - } - if cv.is_type(CommunicationType::get_user_data) { - if let Some(anonymous) = { - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - anonymous_manager::get_anonymous_user(user_id as u64).await - } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { - anonymous_manager::get_anonymous_user_by_name(username.to_string()).await - } else { - None - } - } { - let response = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data( - DataTypes::username, - DataValue::Str(anonymous.get_user_name().await), - ) - .add_data( - DataTypes::user_id, - DataValue::Number(anonymous.get_user_id() as i64), - ) - .add_data( - DataTypes::display, - DataValue::Str(anonymous.get_display_name().await), - ) - .add_data( - DataTypes::avatar, - DataValue::Str(anonymous.get_avatar().await), - ) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); - - self.send_message(&response).await; - - return; - } - } - - if cv.is_type(CommunicationType::change_user_data) - || cv.is_type(CommunicationType::read_notification) - || cv.is_type(CommunicationType::get_notifications) - || cv.is_type(CommunicationType::get_user_data) - || cv.is_type(CommunicationType::get_iota_data) - || cv.is_type(CommunicationType::delete_user) - { - let sender = self.get_user_id().await; - self.handle_omega_forward(cv.with_sender(sender as u64)) - .await; - return; - } - // Forward other messages to Iota - self.forward_to_iota(cv).await; - }); - } - async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { - let client_for_closure = self.clone(); - tokio::spawn(async move { - let response_cv = get_omega_connection() - .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) - .await; - if let Ok(response_cv) = response_cv { - client_for_closure.send_message(&response_cv).await; - } - }); - } - - /// Handle ping message - async fn handle_ping(self: Arc, cv: CommunicationValue) { - // Update our ping if provided - if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - let current = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(); - let mut ping_guard = self.ping.write().await; - *ping_guard = current as i64 - last_ping; - } - - // 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(DataTypes::ping_iota, DataValue::Number(iota_ping)); - - self.send_message(&response).await; - } - - /// Handle client status change - async fn handle_client_changed(self: Arc, cv: CommunicationValue) { - let user_id = self.get_user_id().await; - if let DataValue::Str(_status_str) = cv.get_data(DataTypes::user_state) { - let user_status = UserStatus::user_online; - if let Some(rho_conn) = self.get_rho_connection().await { - OmegaConnection::client_changed( - rho_conn.get_iota_id().await as i64, - user_id as i64, - user_status, - ) - .await; - } - } - } - - /// Handle call invite - async fn handle_call_invite(self: Arc, cv: CommunicationValue) { - let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); - if receiver_id == 0 { - self.send_error_response(cv.get_id(), CommunicationType::error_no_user_id) - .await; - return; - } - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { - Ok(id) => id, - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) - .await; - return; - } - }, - _ => { - self.send_error_response(cv.get_id(), CommunicationType::error_no_call_id) - .await; - return; - } - }; - - let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; - if !invited { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) - .await; - return; - } - - // Find target RhoConnection - let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { - Some(rho) => rho, - _ => { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - // Get sender user ID - let sender_id = self.get_user_id().await; - - // Create and send call distribution message - let forward = CommunicationValue::new(CommunicationType::call_invite) - .with_receiver(receiver_id as u64) - .with_sender(sender_id as u64) - .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) - .add_data( - DataTypes::receiver_id, - DataValue::Str(receiver_id.to_string()), - ) - .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); - - target_rho.message_to_client(forward).await; - - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } - - /// Handle get call request - async fn handle_get_call(self: Arc, cv: CommunicationValue) { - let user_id = self.get_user_id().await; - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { - Ok(id) => id, - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - }, - _ => { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - if let Some(token) = call_manager::get_call_token(user_id, call_id).await { - let response = CommunicationValue::new(CommunicationType::call_token) - .with_id(cv.get_id()) - .with_receiver(user_id as u64) - .add_data(DataTypes::call_token, DataValue::Str(token)); - self.send_message(&response).await; - } else { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - } - async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id().await) - .await - .unwrap() - .has_admin() - { - let _ = call_util::remove_participant(call_id, user_id as u64).await; - call.get_caller(user_id as u64) - .await - .unwrap() - .set_timeout(untill) - .await; - } - } - } - async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id().await) - .await - .unwrap() - .has_admin() - { - call.remove_caller(user_id as u64).await; - } - } - } - async fn handle_call_set_anonymous_joining(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let enable = cv.get_data(DataTypes::enabled).as_bool().unwrap_or(true); - - let call = call_manager::get_call(call_id).await; - - let mut short_link = None; - if let Some(call) = call { - if call - .get_caller(self.get_user_id().await) - .await - .unwrap() - .has_admin() - { - call.set_anonymous_joining(enable).await; - } - short_link = call.get_short_link().await; - } - let mut response_cv = - CommunicationValue::new(CommunicationType::call_set_anonymous_joining) - .with_id(cv.get_id()) - .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) - .add_data(DataTypes::enabled, DataValue::Bool(enable)); - if let Some(short_link) = short_link { - response_cv = response_cv.add_data(DataTypes::link, DataValue::Str(short_link)); - } - self.send_message(&response_cv).await; - } - - /// Forward message to Iota - async fn forward_to_iota(self: Arc, cv: CommunicationValue) { - let sender_user_id = self.get_user_id().await; - let msg_id = cv.get_id(); - let msg_type = cv.get_type(); - - log_in!( - sender_user_id as i64, - PrintType::Client, - "Forwarding client->iota: sender={} type={:?} id={} receiver={}", - sender_user_id, - msg_type, - msg_id, - cv.get_receiver() - ); - - if cv.is_type(CommunicationType::add_conversation) - && cv - .get_data(DataTypes::chat_partner_id) - .as_number() - .is_none() - { - let chat_partner_name = cv - .get_data(DataTypes::chat_partner_name) - .as_str() - .unwrap_or("") - .to_string(); - - if anonymous_manager::get_anonymous_user_by_name(chat_partner_name.to_string()) - .await - .is_some() - { - self.send_error_response(cv.get_id(), CommunicationType::error_anonymous) - .await; - return; - } - - let load_uuid_response = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.clone().get_id()) - .add_data( - DataTypes::username, - DataValue::Str(chat_partner_name.clone()), - ), - Some(Duration::from_secs(20)), - ) - .await; - let chat_partner_id = { - if let Ok(load_uuid_response) = load_uuid_response { - load_uuid_response.get_data(DataTypes::user_id).clone() - } else { - DataValue::Null - } - }; - - if let Some(rho_conn) = self.get_rho_connection().await { - let iota_id = rho_conn.get_iota_id().await; - log_in!( - sender_user_id as i64, - PrintType::Client, - "Resolved rho for add_conversation: sender={} -> iota_id={} id={}", - sender_user_id, - iota_id, - msg_id - ); - - let updated_cv = cv - .with_sender(sender_user_id as u64) - .add_data(DataTypes::chat_partner_id, chat_partner_id); - rho_conn.message_to_iota(updated_cv).await; - } else { - log_err!( - sender_user_id as i64, - PrintType::Client, - "No rho/iota mapping found for add_conversation sender={} type={:?} id={}", - sender_user_id, - msg_type, - msg_id - ); - } - return; - } - - if let Some(rho_conn) = self.get_rho_connection().await { - let iota_id = rho_conn.get_iota_id().await; - log_in!( - sender_user_id as i64, - PrintType::Client, - "Resolved rho for forward: sender={} -> iota_id={} type={:?} id={}", - sender_user_id, - iota_id, - msg_type, - msg_id - ); - - let updated_cv = cv.with_sender(sender_user_id as u64); - rho_conn.message_to_iota(updated_cv).await; - } else { - log_err!( - sender_user_id as i64, - PrintType::Client, - "No rho/iota mapping found for sender={} type={:?} id={}", - sender_user_id, - msg_type, - msg_id - ); - self.send_error_response(msg_id, CommunicationType::error) - .await; - } - } - - /// Send error response - async fn send_error_response(self: Arc, message_id: u32, 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 is_open_guard = self.is_open.write().await; - if !*is_open_guard { - return; - } - *is_open_guard = false; - - let _ = self.sender.close(); - } - - /// Set interested users list - pub async fn set_interested_users(self: Arc, interested_ids: Vec) { - let mut interested_guard = self.interested_users.write().await; - *interested_guard = interested_ids; - } - #[allow(dead_code)] - pub async fn get_interested_users(self: Arc) -> Vec { - let interested_guard = self.interested_users.read().await; - interested_guard.clone() - } - - /// Check if interested in a user and send notification - #[allow(dead_code)] - pub async fn are_you_interested(self: Arc, user_id: i64) { - let interested_guard = self.clone().get_interested_users().await; - if interested_guard.contains(&user_id) { - let notification = CommunicationValue::new(CommunicationType::client_changed) - .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); - - self.send_message(¬ification).await; - } - } - - /// Handle connection close - pub async fn handle_close(&self) { - let user_id = self.get_user_id().await; - if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id as i64).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 { - sender: Arc::clone(&self.sender), - receiver: Arc::clone(&self.receiver), - user_id: self.user_id, - ping: Arc::clone(&self.ping), - pub_key: Arc::clone(&self.pub_key), - rho_connection: Arc::clone(&self.rho_connection), - interested_users: Arc::clone(&self.interested_users), - is_open: Arc::clone(&self.is_open), - } - } -} diff --git a/src/rho/connection.rs b/src/rho/connection.rs deleted file mode 100755 index ca746f0..0000000 --- a/src/rho/connection.rs +++ /dev/null @@ -1,358 +0,0 @@ -use rand::{Rng, distributions::Alphanumeric}; -use std::{sync::Arc, time::Duration}; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Receiver, Sender}; - -use crate::{ - anonymous_clients::anonymous_client_connection::AnonymousClientConnection, - get_private_key, get_public_key, log_cv_in, log_cv_out, log_err, log_in, log_out, - omega::omega_connection::get_omega_connection, - rho::{ - client_connection::ClientConnection, iota_connection::IotaConnection, - rho_connection::RhoConnection, rho_manager, - }, - util::{ - crypto_helper::{load_public_key, public_key_to_base64}, - crypto_util::{DataFormat, SecurePayload}, - logger::PrintType, - }, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] -pub enum ConnectionKind { - Client, - Iota, - AnonymousClient, - Phi, -} - -pub struct GeneralConnection { - pub sender: Arc, - pub receiver: Arc, - - identified: Arc>, - challenged: Arc>, - challenge: Arc>, - - connection_kind: Arc>>, - pub rho_connection: Arc>>>, - id: Arc>, - - pub_key: Arc>>>, -} -impl GeneralConnection { - pub fn new(sender: Sender, receiver: Receiver) -> Arc { - Arc::new(Self { - sender: Arc::new(sender), - receiver: Arc::new(receiver), - identified: Arc::new(RwLock::new(false)), - challenged: Arc::new(RwLock::new(false)), - challenge: Arc::new(RwLock::new(String::new())), - connection_kind: Arc::new(RwLock::new(None)), - rho_connection: Arc::new(RwLock::new(None)), - id: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - }) - } -} -impl GeneralConnection { - pub async fn handle(self: Arc) { - log_in!(0, PrintType::General, "General connection handler started"); - - loop { - let cv = match self.receiver.receive().await { - Ok(v) => v, - Err(_) => { - break; - } - }; - - log_cv_in!(cv); - - if !*self.identified.read().await { - self.handle_identification(cv).await; - continue; - } - - if !*self.challenged.read().await { - self.handle_challenge_response(cv).await; - } - - if *self.challenged.read().await { - let self_clone = self.clone(); - tokio::spawn(async move { - self_clone.migrate().await; - }); - break; - } - } - - log_out!(0, PrintType::General, "General connection handler stopped"); - } - async fn handle_identification(self: &Arc, cv: CommunicationValue) { - if !cv.is_type(CommunicationType::identification) { - return; - } - - if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { - *self.id.write().await = *iota_id as u64; - *self.connection_kind.write().await = Some(ConnectionKind::Iota); - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) - .add_data(DataTypes::iota_id, DataValue::Number(*iota_id)); - - let response_cv = get_omega_connection() - .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) - .await; - - let response_cv = match response_cv { - Ok(r) => r, - Err(_) => { - return; - } - }; - - let base64_pub = response_cv - .get_data(DataTypes::public_key) - .as_str() - .unwrap_or(""); - - let pub_key = match load_public_key(base64_pub) { - Some(pk) => pk, - None => { - return; - } - }; - - *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge.clone(); - *self.identified.write().await = true; - - let encrypted_challenge = - SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(public_key_to_base64(&get_public_key())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); - - log_cv_out!(response); - let _ = self.sender.send(&response).await; - } else if let DataValue::Number(user_id) = cv.get_data(DataTypes::user_id) { - *self.id.write().await = *user_id as u64; - *self.connection_kind.write().await = Some(ConnectionKind::Client); - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) - .add_data(DataTypes::user_id, DataValue::Number(*user_id)); - - let response_cv = get_omega_connection() - .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) - .await; - - let response_cv = match response_cv { - Ok(r) => r, - Err(_) => { - return; - } - }; - - let base64_pub = response_cv - .get_data(DataTypes::public_key) - .as_str() - .unwrap_or(""); - - let pub_key = match load_public_key(base64_pub) { - Some(pk) => pk, - None => { - return; - } - }; - - *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge.clone(); - *self.identified.write().await = true; - - let encrypted_challenge = - SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(public_key_to_base64(&get_public_key())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); - - log_cv_out!(response); - let _ = self.sender.send(&response).await; - } - } - async fn handle_challenge_response(self: &Arc, cv: CommunicationValue) { - let id = *self.id.read().await as i64; - - if !cv.is_type(CommunicationType::challenge_response) { - return; - } - - if let DataValue::Str(response) = cv.get_data(DataTypes::challenge) { - let expected = self.challenge.read().await.clone(); - - if *response == expected { - *self.challenged.write().await = true; - - let response = CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::Bool(true)); - - log_cv_out!(response); - if let Err(_) = self.sender.send(&response).await { - return; - } - } else { - log_err!( - id, - PrintType::Iota, - "Challenge response mismatch expected={} actual={}", - expected, - response - ); - } - } else { - log_err!( - id, - PrintType::Iota, - "Challenge response missing challenge payload" - ); - } - } - - async fn migrate(self: &Arc) -> bool { - let kind = match *self.connection_kind.read().await { - Some(kind) => kind, - None => { - return false; - } - }; - let id = *self.id.read().await; - - match kind { - ConnectionKind::Client => { - let notify = CommunicationValue::new(CommunicationType::user_connected) - .add_data(DataTypes::user_id, DataValue::Number(id as i64)); - get_omega_connection().send_message(¬ify).await; - - let user_id = id as i64; - - let client = ClientConnection::from_general(self.clone(), id).await; - - let mut rho = rho_manager::get_rho_con_for_user(user_id).await; - - if rho.is_none() { - let get_user_msg = CommunicationValue::new(CommunicationType::get_user_data) - .add_data(DataTypes::user_id, DataValue::Number(user_id)); - - if let Ok(user_data_cv) = get_omega_connection() - .await_response(&get_user_msg, Some(Duration::from_secs(20))) - .await - { - if let DataValue::Number(iota_id) = - user_data_cv.get_data(DataTypes::iota_id) - { - if let Some(bound_rho) = - rho_manager::bind_user_to_iota(user_id, *iota_id).await - { - bound_rho.bind_user_id(user_id).await; - rho = Some(bound_rho); - } - } - } - } - - *self.rho_connection.write().await = rho.clone(); - - if let Some(rho_conn) = rho { - rho_conn.bind_user_id(user_id).await; - rho_conn.add_client_connection(client.clone()).await; - } else { - log_err!( - user_id, - PrintType::Client, - "No RhoConnection found for user {}, client not attached to iota", - id - ); - } - - client.start(); - } - ConnectionKind::Iota => { - let notify = CommunicationValue::new(CommunicationType::iota_connected) - .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); - get_omega_connection().send_message(¬ify).await; - - let iota = IotaConnection::from_general(self.clone(), id).await; - - let rho = Arc::new(RhoConnection::new(iota.clone(), Vec::new()).await); - - iota.set_rho_connection(rho.clone()).await; - - rho_manager::add_rho(rho).await; - - let get_iota_msg = CommunicationValue::new(CommunicationType::get_iota_data) - .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); - - if let Ok(iota_data_cv) = get_omega_connection() - .await_response(&get_iota_msg, Some(Duration::from_secs(20))) - .await - { - if let DataValue::Array(users) = iota_data_cv.get_data(DataTypes::user_ids) { - let mut user_ids: Vec = Vec::new(); - for value in users { - if let DataValue::Number(user_id) = value { - user_ids.push(*user_id as u64); - } - } - iota.set_user_ids(user_ids).await; - } - } - - iota.clone().start(); - } - ConnectionKind::AnonymousClient => { - let client = AnonymousClientConnection::from_general(self.clone(), id).await; - client.start(); - } - ConnectionKind::Phi => { - let phi = ClientConnection::from_general(self.clone(), id).await; - phi.start(); - } - } - true - } -} diff --git a/src/rho/iota_connection.rs b/src/rho/iota_connection.rs deleted file mode 100755 index 41336ea..0000000 --- a/src/rho/iota_connection.rs +++ /dev/null @@ -1,461 +0,0 @@ -use crate::calls::call_group::CallGroup; -use crate::calls::call_manager; -use crate::log_cv_in; -use crate::log_cv_out; -use crate::log_err; -use crate::log_in; -use crate::omega::omega_connection::get_omega_connection; -use crate::rho::connection::GeneralConnection; -use crate::util::logger::PrintType; -use dashmap::DashMap; -use std::collections::BTreeMap; -use std::{collections::HashMap, sync::Arc, time::Duration}; -use tokio::sync::RwLock; -use tokio::sync::mpsc; -use ttp_core::CommunicationType; -use ttp_core::CommunicationValue; -use ttp_core::DataTypes; -use ttp_core::DataValue; -use ttp_native::Receiver; -use ttp_native::Sender; -use x448::PublicKey; - -use super::{rho_connection::RhoConnection, rho_manager}; -use crate::omega::omega_connection::OmegaConnection; - -#[allow(dead_code)] -pub struct IotaConnection { - pub iota_id: u64, - pub sender: Arc, - pub receiver: Arc, - pub user_ids: Arc>>, - pub ping: Arc>, - pub_key: Arc>>>, - pub waiting_tasks: - DashMap, CommunicationValue) -> bool + Send + Sync>>, - pub rho_connection: Arc>>>, -} - -impl IotaConnection { - pub async fn from_general(general: Arc, iota_id: u64) -> Arc { - Arc::new(Self { - ping: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - rho_connection: general.rho_connection.clone(), - user_ids: Arc::new(RwLock::new(Vec::new())), - sender: general.sender.clone(), - receiver: general.receiver.clone(), - iota_id: iota_id, - waiting_tasks: DashMap::new(), - }) - } - pub fn start(self: Arc) { - let self_clone = self.clone(); - tokio::spawn(async move { - loop { - match self_clone.receiver.receive().await { - Ok(cv) => { - self_clone.clone().handle_message(cv).await; - } - Err(_) => { - break; - } - } - } - self_clone.handle_close().await; - }); - } - - /// Get the Iota ID - pub async fn get_iota_id(&self) -> u64 { - self.iota_id - } - - #[allow(dead_code)] - pub async fn get_public_key(&self) -> Option { - if let Some(public_key) = self.pub_key.read().await.clone() { - PublicKey::from_bytes(&public_key) - } else { - None - } - } - - /// Get the user IDs - pub async fn get_user_ids(&self) -> Vec { - self.user_ids.read().await.clone() - } - - /// Replace all users linked to this iota and synchronize the attached rho mapping. - pub async fn set_user_ids(&self, user_ids: Vec) { - { - let mut guard = self.user_ids.write().await; - *guard = user_ids.clone(); - } - - if let Some(rho_conn) = self.get_rho_connection().await { - let user_ids_i64: Vec = user_ids.into_iter().map(|u| u as i64).collect(); - rho_conn.set_user_ids(user_ids_i64).await; - } - } - - pub async fn add_user_id(&self, user_id: u64) { - let mut should_sync = false; - { - let mut guard = self.user_ids.write().await; - if !guard.contains(&user_id) { - guard.push(user_id); - should_sync = true; - } - } - - if should_sync { - if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn.add_user_id(user_id as i64).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: Arc) { - 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() { - Some(weak_ref.clone()) - } else { - None - } - } - - /// Send a CommunicationValue to the Iota - pub async fn send_message(&self, cv: &CommunicationValue) { - if !cv.is_type(CommunicationType::pong) { - log_cv_out!(PrintType::Iota, cv); - } - if let Err(e) = self.sender.send(&cv).await { - log_err!( - self.iota_id as i64, - PrintType::Iota, - "Failed to send message: {:?}", - e - ); - } - } - - /// Handle incoming message from Iota - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - let msg_id = cv.get_id(); - if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { - if (task)(self.clone(), cv.clone()) { - return; - } - } - - // Handle ping - if cv.is_type(CommunicationType::ping) || cv.is_type(CommunicationType::pong) { - self.handle_ping(cv).await; - return; - } - - log_cv_in!(PrintType::Iota, cv); - - // Handle GET_CHATS - if cv.is_type(CommunicationType::get_chats) { - self.handle_get_chats(cv).await; - return; - } - - // Handle forwarding to other Iotas or clients - let receiver_id = cv.get_receiver(); - if (receiver_id != 0 && !self.get_user_ids().await.contains(&(receiver_id as u64))) - || cv.is_type(CommunicationType::message_other_iota) - || cv.is_type(CommunicationType::send_chat) - { - self.handle_forward_message(cv).await; - return; - } - - if cv.is_type(CommunicationType::change_iota_data) - || cv.is_type(CommunicationType::push_notification) - || cv.is_type(CommunicationType::get_user_data) - || cv.is_type(CommunicationType::get_iota_data) - || cv.is_type(CommunicationType::get_register) - || cv.is_type(CommunicationType::complete_register_user) - || cv.is_type(CommunicationType::delete_iota) - { - let sender = self.get_iota_id().await; - self.handle_omega_forward(cv.with_sender(sender as u64)) - .await; - return; - } - self.forward_to_client(cv).await; - } - - #[allow(dead_code)] - async fn send_error_response(&self, message_id: u32, error_type: CommunicationType) { - let error = CommunicationValue::new(error_type).with_id(message_id); - self.send_message(&error).await; - } - - #[allow(dead_code)] - async fn close(&self) { - let _ = self.sender.close(); - } - - async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { - let iota_for_closure = self.clone(); - tokio::spawn(async move { - let response_cv = get_omega_connection() - .await_response(&cv.with_sender(self.iota_id), Some(Duration::from_secs(20))) - .await; - if let Ok(response_cv) = response_cv { - iota_for_closure.send_message(&response_cv).await; - } - }); - } - /// Handle ping message - async fn handle_ping(&self, cv: CommunicationValue) { - if let DataValue::Number(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; - } - } - - let client_pings = if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn.get_client_pings().await - } else { - HashMap::new() - }; - - let pings: Vec<(DataTypes, DataValue)> = client_pings - .into_iter() - .map(|(k, v)| (DataTypes::parse(k), DataValue::Number(v))) - .collect(); - let response = CommunicationValue::new(CommunicationType::pong) - .with_id(cv.get_id()) - .add_data(DataTypes::ping_clients, DataValue::Container(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(); - let sender_id = cv.get_sender(); - let my_user_ids = self.get_user_ids().await; - - log_in!( - self.iota_id as i64, - PrintType::Iota, - "Authority check: sender_id={} receiver_id={} iota_user_ids={:?} msg_type={:?} msg_id={}", - sender_id, - receiver_id, - my_user_ids, - cv.get_type(), - cv.get_id() - ); - - if my_user_ids.contains(&(sender_id as u64)) { - if let Some(target_rho) = rho_manager::get_rho_con_for_user(receiver_id as i64).await { - target_rho.message_to_iota(cv).await; - } else { - let error = CommunicationValue::new(CommunicationType::error_no_iota) - .with_id(cv.get_id()) - .with_sender(cv.get_sender()); - self.send_message(&error).await; - } - } else { - log_err!( - self.iota_id as i64, - PrintType::Iota, - "Rejected client->iota forward: sender_id={} is not authorized for this iota. Known users={:?}", - sender_id, - my_user_ids - ); - - self.send_message( - &CommunicationValue::new(CommunicationType::error_invalid_user_id).add_data( - DataTypes::error_type, - DataValue::Str( - "You are sending to another User without authority.".to_string(), - ), - ), - ) - .await; - } - } - - /// Handle GET_CHATS message - async fn handle_get_chats(&self, cv: CommunicationValue) { - let receiver_id = cv.get_receiver(); - let mut interested_ids: Vec = Vec::new(); - - // ============================ - // Load Calls - // ============================ - let calls: Vec> = call_manager::get_call_groups(receiver_id).await; - - let mut invites: HashMap> = HashMap::new(); - let empty = calls.is_empty(); - - for call in calls { - for inviter in call.members.read().await.iter() { - let call_self = call.get_caller(receiver_id).await.unwrap(); - - let inviter_id = inviter.user_id; - let timeout = *call_self.timeout.read().await; - let admin = call_self.has_admin(); - - // Build call container - let mut call_map: BTreeMap = BTreeMap::new(); - - call_map.insert(DataTypes::call_id, DataValue::Str(call.call_id.to_string())); - - if timeout > 0 { - call_map.insert(DataTypes::timeout, DataValue::Number(timeout as i64)); - } - - if admin { - call_map.insert(DataTypes::has_admin, DataValue::Bool(true)); - } - - let call_container = DataValue::container_from_map(&call_map); - - invites - .entry(inviter_id as i64) - .or_insert_with(Vec::new) - .push(call_container); - } - } - - // ============================ - // Enrich Contacts - // ============================ - let enriched_contacts = if empty { - match cv.get_data(DataTypes::user_ids) { - DataValue::Array(arr) => DataValue::Array(arr.clone()), - _ => DataValue::Array(vec![]), - } - } else { - let mut enriched: Vec = Vec::new(); - - if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { - for user_val in users { - if let DataValue::Container(entries) = user_val { - let mut user_map: BTreeMap = - entries.iter().cloned().collect(); - - // extract user_id - if let Some(DataValue::Number(user_id)) = user_map.get(&DataTypes::user_id) - { - interested_ids.push(*user_id); - - // attach calls if exists - if let Some(call_list) = invites.get(user_id) { - user_map - .insert(DataTypes::calls, DataValue::Array(call_list.clone())); - } - } - - enriched.push(DataValue::container_from_map(&user_map)); - } - } - } - - DataValue::Array(enriched) - }; - - // ============================ - // Notify Omega - // ============================ - OmegaConnection::user_states(receiver_id as i64, interested_ids.clone()).await; - - // ============================ - // Notify Rho - // ============================ - if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn - .set_interested(receiver_id as i64, interested_ids) - .await; - } - - // ============================ - // Forward to client - // ============================ - self.forward_to_client(cv.add_data(DataTypes::user_ids, enriched_contacts)) - .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); - rho_conn.message_to_client(updated_cv).await; - } else { - } - } - - pub async fn handle_close(&self) { - if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn.close_iota_connection().await; - } - } - - #[allow(dead_code)] - pub async fn await_response( - self: Arc, - cv: &CommunicationValue, - timeout_duration: Option, - ) -> Result { - let (tx, mut rx) = mpsc::channel(1); - let msg_id = cv.get_id(); - - let task_tx = tx.clone(); - self.waiting_tasks.insert( - msg_id, - Box::new(move |_, response_cv| { - let inner_tx = task_tx.clone(); - tokio::spawn(async move { - let _ = inner_tx.send(response_cv).await; - }); - true - }), - ); - - self.send_message(cv).await; - - let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); - - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response_cv)) => Ok(response_cv), - Ok(_) => Err("Failed to receive response, channel was closed.".to_string()), - Err(_) => { - self.waiting_tasks.remove(&msg_id); - Err(format!( - "Request timed out after {} seconds.", - timeout.as_secs() - )) - } - } - } -} - -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 deleted file mode 100644 index ac498f3..0000000 --- a/src/rho/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod client_connection; -pub mod connection; -pub mod iota_connection; -pub mod rho_connection; -pub mod rho_manager; -pub mod server; diff --git a/src/rho/rho_connection.rs b/src/rho/rho_connection.rs deleted file mode 100644 index 6a740ff..0000000 --- a/src/rho/rho_connection.rs +++ /dev/null @@ -1,199 +0,0 @@ -use super::{client_connection::ClientConnection, iota_connection::IotaConnection, rho_manager}; - -use crate::data::user::UserStatus; -use crate::omega::omega_connection::OmegaConnection; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; - -pub struct RhoConnection { - iota_connection: Arc, - user_ids: Arc>>, - 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: Arc::new(RwLock::new(user_ids.clone())), - client_connections: Arc::new(RwLock::new(Vec::new())), - }; - - rho_connection - } - - pub async fn get_iota_id(&self) -> u64 { - self.iota_connection.iota_id - } - - pub async fn get_user_ids(&self) -> Vec { - self.user_ids.read().await.clone() - } - - pub async fn set_user_ids(&self, user_ids: Vec) { - let mut guard = self.user_ids.write().await; - *guard = user_ids; - } - - pub async fn add_user_id(&self, user_id: i64) { - let mut guard = self.user_ids.write().await; - if !guard.contains(&user_id) { - guard.push(user_id); - } - } - - pub async fn bind_user_id(&self, user_id: i64) { - self.add_user_id(user_id).await; - self.iota_connection.add_user_id(user_id as u64).await; - } - - pub fn get_iota_connection(&self) -> &Arc { - &self.iota_connection - } - - 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: i64, - ) -> Vec> { - let connections = self.client_connections.read().await; - let mut collections = Vec::new(); - for con in connections.iter() { - if con.get_user_id().await == user_id as u64 { - collections.push(con.clone()); - } - } - collections - } - - /// Add a client connection - #[allow(dead_code)] - pub async fn add_client_connection(&self, connection: Arc) { - let notification = CommunicationValue::new(CommunicationType::client_connected).add_data( - DataTypes::user_id, - DataValue::Number(connection.get_user_id().await as i64), - ); - - self.iota_connection.send_message(¬ification).await; - - { - let mut connections = self.client_connections.write().await; - connections.push(Arc::clone(&connection)); - } - - OmegaConnection::client_changed( - self.get_iota_id().await as i64, - connection.get_user_id().await as i64, - UserStatus::user_online, - ) - .await; - } - - /// Remove a client connection - pub async fn close_client_connection(&self, connection: Arc) { - let target_user_id = connection.get_user_id().await; - { - let mut connections = self.client_connections.write().await; - connections.retain(|con| { - futures::executor::block_on(async { con.get_user_id().await != target_user_id }) - }); - } - - // Notify OmegaConnection - OmegaConnection::client_changed( - self.get_iota_id().await as i64, - connection.get_user_id().await as i64, - UserStatus::user_offline, - ) - .await; - } - - /// 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 as i64).await; - - // Notify OmegaConnection - OmegaConnection::close_iota(self.get_iota_id().await as i64).await; - } - - /// Send message from Iota to specific client - pub async fn message_to_client(&self, cv: CommunicationValue) { - let connections = self.client_connections.read().await; - let receiver_id = cv.get_receiver(); - for connection in connections.iter() { - if connection.get_user_id().await == receiver_id { - connection.clone().send_message(&cv).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: i64, interested_ids: Vec) { - let connections = self.client_connections.read().await; - for connection in connections.iter() { - let conn_user_id = connection.get_user_id().await; - if conn_user_id == user_id as u64 { - connection - .clone() - .set_interested_users(interested_ids.clone()) - .await; - break; - } - } - } - - /// Check if clients are interested in a user - #[allow(dead_code)] - pub async fn are_they_interested(&self, user_id: i64) { - let connections = self.client_connections.read().await; - for connection in connections.iter() { - connection.clone().are_you_interested(user_id).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() { - let 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 - #[allow(dead_code)] - pub async fn contains_user(&self, user_id: &i64) -> bool { - self.user_ids.read().await.contains(user_id) - } - - /// Get count of active client connections - #[allow(dead_code)] - pub async fn client_count(&self) -> usize { - let connections = self.client_connections.read().await; - connections.len() - } -} diff --git a/src/rho/rho_manager.rs b/src/rho/rho_manager.rs deleted file mode 100644 index e823a04..0000000 --- a/src/rho/rho_manager.rs +++ /dev/null @@ -1,83 +0,0 @@ -use super::rho_connection::RhoConnection; -use crate::log_in; -use crate::util::logger::PrintType; -use std::{ - collections::HashMap, - sync::{Arc, LazyLock}, -}; -use tokio::sync::RwLock; - -pub static RHO_CONNECTIONS: LazyLock>>>> = - LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); - -pub async fn get_rho_con_for_user(user_id: i64) -> Option> { - let connections = RHO_CONNECTIONS.read().await; - for rho_connection in connections.values() { - let rho_user_ids = rho_connection.get_user_ids().await; - log_in!( - user_id, - PrintType::Client, - "Comparing user IDs: {:?}", - rho_user_ids - ); - if rho_user_ids.contains(&user_id) { - return Some(Arc::clone(rho_connection)); - } - } - None -} - -#[allow(dead_code)] -pub async fn contains_iota(iota_id: i64) -> bool { - let connections = RHO_CONNECTIONS.read().await; - connections.contains_key(&iota_id) -} - -/// Bind a user ID to an already tracked iota/rho connection. -pub async fn bind_user_to_iota(user_id: i64, iota_id: i64) -> Option> { - let connections = RHO_CONNECTIONS.read().await; - if let Some(rho_connection) = connections.get(&iota_id) { - let rho = Arc::clone(rho_connection); - drop(connections); - - rho.add_user_id(user_id).await; - - log_in!( - user_id, - PrintType::Client, - "Bound user {} to iota {}", - user_id, - iota_id - ); - - Some(rho) - } else { - None - } -} - -/// Remove a RhoConnection by Iota ID -pub async fn remove_rho(iota_id: i64) -> 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 as i64, rho_connection); -} - -/// Get a RhoConnection by Iota ID directly -#[allow(dead_code)] -pub async fn get_rho_by_iota(iota_id: i64) -> Option> { - let connections = RHO_CONNECTIONS.read().await; - connections.get(&iota_id).map(Arc::clone) -} - -/// Get the count of active connections -pub async fn connection_count() -> usize { - let connections = RHO_CONNECTIONS.read().await; - connections.len() -} diff --git a/src/rho/server.rs b/src/rho/server.rs deleted file mode 100644 index 9e3946d..0000000 --- a/src/rho/server.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::{ - log, - rho::connection::GeneralConnection, - util::{file_util::load_file_vec, logger::PrintType}, -}; -use ttp_native::Host; - -pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); - - let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); - - let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; - log!(0, PrintType::General, "Server listening on port {}", port); - - while let Some((sender, receiver)) = host.next().await { - tokio::spawn(async move { - let conn = GeneralConnection::new(sender, receiver); - conn.handle().await; - }); - } - log!(0, PrintType::General, "Server stopped"); - - Ok(()) -} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs deleted file mode 100755 index cb3ae6a..0000000 --- a/src/util/crypto_helper.rs +++ /dev/null @@ -1,149 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, OsRng}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use rand_core::RngCore; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret, SharedSecret}; - -/// Errors for crypto operations -#[allow(dead_code)] -#[derive(Debug)] -pub enum CryptoError { - Base64Decode(base64::DecodeError), - InvalidKey, - AgreementError, - EncryptionError(aes_gcm::Error), - DecryptionError(aes_gcm::Error), -} - -impl From for CryptoError { - fn from(err: base64::DecodeError) -> Self { - CryptoError::Base64Decode(err) - } -} - -#[allow(dead_code)] -pub struct KeyPair { - pub secret: Secret, - pub public: PublicKey, -} - -#[allow(dead_code)] -pub fn generate_keypair() -> KeyPair { - let mut buf = [0u8; 56]; - let mut rng = OsRng; - rng.fill_bytes(&mut buf); - let secret = Secret::from_bytes(&buf).unwrap(); - let public = PublicKey::from(&secret); - KeyPair { secret, public } -} - -pub fn public_key_to_base64(pubkey: &PublicKey) -> String { - STANDARD.encode(pubkey.as_bytes().as_ref()) -} - -pub fn secret_key_to_base64(secret: &Secret) -> String { - STANDARD.encode(secret.as_bytes().as_ref()) -} - -pub fn load_public_key(base64_pub: &str) -> Option { - let bytes = STANDARD.decode(base64_pub).unwrap(); - PublicKey::from_bytes(&bytes) -} - -pub fn load_secret_key(base64_secret: &str) -> Option { - let bytes = STANDARD.decode(base64_secret).unwrap(); - Secret::from_bytes(&bytes) -} - -fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(shared.as_bytes()); - let result = hasher.finalize(); - let mut key = [0u8; 32]; - key.copy_from_slice(&result[..32]); - key -} - -#[allow(dead_code)] -pub fn encrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - plaintext: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - encrypt(secret, peer_pub, plaintext) -} -pub fn encrypt( - secret: Secret, - peer_pub: PublicKey, - plaintext: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - let mut nonce_bytes = [0u8; 12]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - let ciphertext = cipher - .encrypt(nonce, plaintext.as_bytes()) - .map_err(CryptoError::EncryptionError)?; - // prefix nonce to ciphertext - let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); - out.extend_from_slice(&nonce_bytes); - out.extend_from_slice(&ciphertext); - Ok(STANDARD.encode(&out)) -} - -pub fn decrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - encrypted_base64: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - decrypt(secret, peer_pub, encrypted_base64) -} -pub fn decrypt( - secret: Secret, - peer_pub: PublicKey, - encrypted_base64: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - - let encrypted = STANDARD.decode(encrypted_base64)?; - if encrypted.len() < 12 { - return Err(CryptoError::DecryptionError(aes_gcm::Error)); - } - let nonce_bytes = &encrypted[..12]; - let ciphertext = &encrypted[12..]; - let nonce = Nonce::from_slice(nonce_bytes); - let plaintext_bytes = cipher - .decrypt(nonce, ciphertext) - .map_err(CryptoError::DecryptionError)?; - let plaintext = String::from_utf8(plaintext_bytes) - .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; - Ok(plaintext) -} - -#[allow(dead_code)] -pub fn hash_it(input: &str) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(input.as_bytes()); - hasher.finalize().to_vec() -} - -#[allow(dead_code)] -pub fn hex_hash(input: &str) -> String { - let digest = hash_it(input); - digest.iter().map(|b| format!("{:02x}", b)).collect() -} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs deleted file mode 100644 index bb784e8..0000000 --- a/src/util/crypto_util.rs +++ /dev/null @@ -1,178 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, Payload}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; -use hkdf::Hkdf; -type HkdfSha256 = sha2::Sha256; -use sha2::{Digest, Sha256 as HashSha256}; -use x448::{PublicKey, Secret}; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum SecurePayloadError { - InvalidBase64, - InvalidHex, - EncryptionError, - DecryptionError, - InvalidKeyLength, -} - -#[derive(Clone, Copy, Debug)] -#[allow(dead_code)] -pub enum DataFormat { - Raw, - Base64, - Hex, -} - -pub struct SecurePayload { - inner_data: Vec, - private_key: Secret, -} - -impl Clone for SecurePayload { - fn clone(&self) -> Self { - Self { - inner_data: self.inner_data.clone(), - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - } - } -} - -#[allow(dead_code)] -impl SecurePayload { - pub fn new>( - data: T, - format: DataFormat, - private_key: S, - ) -> Result - where - S: Into, - { - let raw_data = match format { - DataFormat::Raw => data.as_ref().to_vec(), - DataFormat::Base64 => BASE64_STD - .decode(data.as_ref()) - .map_err(|_| SecurePayloadError::InvalidBase64)?, - DataFormat::Hex => { - hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? - } - }; - - Ok(Self { - inner_data: raw_data, - private_key: private_key.into(), - }) - } - - pub fn get_public_key(&self) -> [u8; 56] { - *PublicKey::from(&self.private_key).as_bytes() - } - - pub fn export(&self, format: DataFormat) -> String { - match format.into() { - DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), - DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), - DataFormat::Hex => hex::encode(&self.inner_data), - } - } - - pub fn get_bytes(&self) -> &[u8] { - &self.inner_data - } - - pub fn get_hash(&self, format: DataFormat) -> String { - let mut hasher = HashSha256::new(); - hasher.update(&self.inner_data); - let result = hasher.finalize(); - - match format { - DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), - DataFormat::Base64 => BASE64_STD.encode(result), - DataFormat::Hex => hex::encode(result), - } - } - - pub fn encrypt_x448(&self, public_key: S) -> Result - where - S: Into, - { - let peer_pub = public_key.into(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; - - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let ciphertext = cipher - .encrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - Ok(SecurePayload { - inner_data: ciphertext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } - - pub fn decrypt_to_format( - &self, - peer_public_key_bytes: &[u8; 56], - output_format: DataFormat, - ) -> Result { - let decrypted_instance = - self.decrypt_x448(PublicKey::from_bytes(peer_public_key_bytes).unwrap())?; - Ok(decrypted_instance.export(output_format)) - } - - pub fn decrypt_x448( - &self, - peer_public_key_bytes: S, - ) -> Result - where - S: Into, - { - let peer_pub = peer_public_key_bytes.into(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let plaintext = cipher - .decrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - Ok(SecurePayload { - inner_data: plaintext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } -} diff --git a/src/util/file_util.rs b/src/util/file_util.rs deleted file mode 100644 index cd214b1..0000000 --- a/src/util/file_util.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::fs::{self, File}; -use std::io::{self, BufReader, Read}; -use std::path::{Path, PathBuf}; - -use crate::log; -use crate::util::logger::PrintType; - -#[allow(dead_code)] -pub fn delete_directory(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - delete_dir_recursive(&dir) -} - -#[allow(dead_code)] -fn delete_dir_recursive(directory: &Path) -> bool { - if !directory.exists() { - return false; - } - if let Err(e) = fs::remove_dir_all(directory) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't delete directory {}: {}", - directory.display(), - e, - ); - return false; - } - true -} - -#[allow(dead_code)] -pub fn delete_user_directory(user_id: i64) { - let user_dir = Path::new(&get_directory()) - .join("users") - .join(user_id.to_string()); - let _ = delete_dir_recursive(&user_dir); -} - -#[allow(dead_code)] -pub fn load_file_buf(path: &str, name: &str) -> io::Result> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - // Ensure the directory exists, create if necessary - if !dir.exists() { - if let Err(_) = fs::create_dir_all(&dir) { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "Directory creation failed", - )); - } - } - - // Create the file if it doesn't exist - if !file_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "File creation failed", - )); - } - - // Open the file and return a BufReader for efficient reading - let file = File::open(&file_path)?; - Ok(BufReader::new(file)) -} -#[allow(dead_code)] -pub fn has_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - if !dir.exists() { - return false; - } - - if !file_path.exists() { - return false; - } - - true -} -#[allow(dead_code)] -pub fn has_dir(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - - if !dir.exists() { - return false; - } - - true -} - -#[allow(dead_code)] -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) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't create directories: {}", - e - ); - return String::new(); - } - return String::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!( - 0, - PrintType::General, - "[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 load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - std::fs::read(file_path) -} - -#[allow(dead_code)] -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) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't create directories: {}", - e - ); - return; - } - } - - if let Err(e) = fs::write(&file_path, value) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't write file {}: {}", - file_path.display(), - e - ); - } -} - -#[allow(dead_code)] -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() -} diff --git a/src/util/logger.rs b/src/util/logger.rs deleted file mode 100644 index 9e9a085..0000000 --- a/src/util/logger.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::{ - collections::BTreeMap, - fs::{self, OpenOptions}, - io::Write, - path::Path, - sync::{OnceLock, mpsc}, - thread, - time::{SystemTime, UNIX_EPOCH}, -}; - -use ansi_term::Color; -use ttp_core::{CommunicationValue, DataTypes, DataValue}; - -static LOGGER: OnceLock> = OnceLock::new(); - -#[allow(dead_code)] -#[derive(Clone, Copy)] -pub enum PrintType { - Call, - Client, - Iota, - Omikron, - Omega, - General, -} - -struct LogMessage { - timestamp_ms: u128, - sender: Option, - prefix: &'static str, - kind: PrintType, - is_error: bool, - message: String, -} - -pub fn startup() { - let (tx, rx) = mpsc::channel::(); - LOGGER.set(tx).expect("Logger already initialized"); - - thread::spawn(move || { - let log_dir = Path::new("logs"); - fs::create_dir_all(log_dir).expect("Failed to create log directory"); - - let start_ts = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - let path = log_dir.join(format!("log_{}.txt", start_ts)); - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(path) - .expect("Failed to open log file"); - - for msg in rx { - let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); - let sender = match msg.sender { - Some(id) => fixed_box(&id.to_string(), 19), - _ => fixed_box("", 19), - }; - - let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); - - println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); - - let _ = writeln!(file, "{}", line); - } - }); -} - -fn colorize(kind: PrintType, is_error: bool) -> Color { - if is_error { - return Color::Red; - } - - match kind { - PrintType::Call => Color::Purple, - PrintType::Client => Color::Green, - PrintType::Iota => Color::Yellow, - PrintType::Omikron => Color::Blue, - PrintType::Omega => Color::Cyan, - PrintType::General => Color::White, - } -} - -fn fixed_box(content: &str, width: usize) -> String { - let s: String = content.chars().take(width).collect(); - let len = s.chars().count(); - if len < width { - format!("[{}{}]", " ".repeat(width - len), s) - } else { - s - } -} - -pub fn log_internal( - sender: i64, - kind: PrintType, - prefix: &'static str, - is_error: bool, - message: String, -) { - let sender = if sender == 0 { None } else { Some(sender) }; - - if let Some(tx) = LOGGER.get() { - let _ = tx.send(LogMessage { - timestamp_ms: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(), - sender, - prefix, - kind, - is_error, - message, - }); - } -} -#[macro_export] -macro_rules! log { - ($sender: expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, "", false, format!($($arg)*)) - }; -} -#[macro_export] -macro_rules! log_in { - ($sender: expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, ">", false, format!($($arg)*)) - }; -} -#[macro_export] -macro_rules! log_out { - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, "<", false, format!($($arg)*)) - }; -} -#[macro_export] -macro_rules! log_err { - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, ">>", true, format!($($arg)*)) - }; -} - -// ******** COMMUNICATION VALUES ******** -pub fn log_cv_internal( - prefix: &'static str, - cv: &CommunicationValue, - print_type: Option, -) { - let formatted = format_cv(cv); - - log_internal( - cv.get_sender() as i64, - print_type.unwrap_or(PrintType::General), - prefix, - false, - formatted, - ); -} - -pub fn format_cv(cv: &CommunicationValue) -> String { - let mut parts = Vec::new(); - - let sender = cv.get_sender(); - let receiver = cv.get_receiver(); - - if sender > 0 && receiver > 0 { - parts.push(format!("{} > {}", sender, receiver)); - } else if sender > 0 { - parts.push(format!("{}", sender)); - } else if receiver > 0 { - parts.push(format!("> {}", receiver)); - } - - let comm_type = cv.get_type().to_string(); - parts.push(format!("{}", comm_type)); - - let data: &BTreeMap = cv.get_data_container(); - - let formated_data = - format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); - - parts.push(format!("{}", formated_data)); - - parts.join(": ") -} - -fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { - let parts: Vec = data - .into_iter() - .map(|(key, value)| { - let key_str = key.to_string(); - - match value { - DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), - - DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); - format!("{}={{ {} }}", key_str, inner_formatted) - } - - DataValue::Array(arr) => { - let arr_formatted = format_array(arr); - format!("{}=[{}]", key_str, arr_formatted) - } - - DataValue::Bool(b) => format!("{}={}", key_str, b), - - DataValue::BoolTrue => format!("{}=true", key_str), - DataValue::BoolFalse => format!("{}=false", key_str), - - DataValue::Number(num) => format!("{}={}", key_str, num), - - _ => "".to_string(), - } - }) - .collect(); - - parts.join(", ") -} - -fn format_array(arr: Vec) -> String { - let parts: Vec = arr - .into_iter() - .map(|value| match value { - DataValue::Str(s) => format!("\"{}\"", s), - - DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); - format!("{{ {} }}", inner_formatted) - } - - DataValue::Array(inner_arr) => { - let formatted = format_array(inner_arr); - format!("[{}]", formatted) - } - - DataValue::Bool(b) => b.to_string(), - - DataValue::BoolTrue => "true".to_string(), - DataValue::BoolFalse => "false".to_string(), - - DataValue::Number(num) => num.to_string(), - - _ => String::new(), - }) - .collect(); - - parts.join(", ") -} - -#[macro_export] -macro_rules! log_cv { - ($kind:expr, $cv:expr) => { - $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) - }; - ($cv:expr) => { - $crate::util::logger::log_cv_internal("", &$cv, None) - }; -} - -#[macro_export] -macro_rules! log_cv_in { - ($kind:expr, $cv:expr) => { - $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) - }; - ($cv:expr) => { - $crate::util::logger::log_cv_internal("> ", &$cv, None) - }; -} -#[macro_export] -macro_rules! log_cv_out { - ($kind:expr, $cv:expr) => { - $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) - }; - ($cv:expr) => { - $crate::util::logger::log_cv_internal("< ", &$cv, None) - }; -} diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index 1897223..0000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod crypto_helper; -pub mod crypto_util; -pub mod file_util; -pub mod logger; From 686c9d78c121ecc06b10e245ba48fda69d255d7a Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 2 Apr 2026 22:28:07 +0200 Subject: [PATCH 102/220] Nuke repo --- .gitignore | 19 - Cargo.lock | 3910 ----------------- Cargo.toml | 37 - LICENSE | 16 - README.md | 1 - .../anonymous_client_connection.rs | 519 --- src/anonymous_clients/anonymous_manager.rs | 53 - src/anonymous_clients/mod.rs | 2 - src/calls/call_group.rs | 106 - src/calls/call_manager.rs | 97 - src/calls/call_util.rs | 151 - src/calls/caller.rs | 49 - src/calls/mod.rs | 4 - src/data/mod.rs | 1 - src/data/user.rs | 30 - src/main.rs | 51 - src/omega/mod.rs | 1 - src/omega/omega_connection.rs | 741 ---- src/omega/ping_pong_task.rs | 41 - src/rho/client_connection.rs | 583 --- src/rho/connection.rs | 358 -- src/rho/iota_connection.rs | 461 -- src/rho/mod.rs | 6 - src/rho/rho_connection.rs | 199 - src/rho/rho_manager.rs | 83 - src/rho/server.rs | 25 - src/util/crypto_helper.rs | 149 - src/util/crypto_util.rs | 178 - src/util/file_util.rs | 185 - src/util/logger.rs | 280 -- src/util/mod.rs | 4 - 31 files changed, 8340 deletions(-) delete mode 100644 .gitignore delete mode 100644 Cargo.lock delete mode 100644 Cargo.toml delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 src/anonymous_clients/anonymous_client_connection.rs delete mode 100644 src/anonymous_clients/anonymous_manager.rs delete mode 100644 src/anonymous_clients/mod.rs delete mode 100644 src/calls/call_group.rs delete mode 100644 src/calls/call_manager.rs delete mode 100644 src/calls/call_util.rs delete mode 100644 src/calls/caller.rs delete mode 100644 src/calls/mod.rs delete mode 100644 src/data/mod.rs delete mode 100644 src/data/user.rs delete mode 100644 src/main.rs delete mode 100644 src/omega/mod.rs delete mode 100644 src/omega/omega_connection.rs delete mode 100644 src/omega/ping_pong_task.rs delete mode 100644 src/rho/client_connection.rs delete mode 100755 src/rho/connection.rs delete mode 100755 src/rho/iota_connection.rs delete mode 100644 src/rho/mod.rs delete mode 100644 src/rho/rho_connection.rs delete mode 100644 src/rho/rho_manager.rs delete mode 100644 src/rho/server.rs delete mode 100755 src/util/crypto_helper.rs delete mode 100644 src/util/crypto_util.rs delete mode 100644 src/util/file_util.rs delete mode 100644 src/util/logger.rs delete mode 100644 src/util/mod.rs diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1baef92..0000000 --- a/.gitignore +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -debug -target - -# These are backup files generated by rustfmt -**/*.rs.bk - -# MSVC Windows builds of rustc generate these, which store debugging information -*.pdb -*.env -# Generated by cargo mutants -# Contains mutation testing data -**/mutants.out*/W - - -# Added by cargo -/target -/logs diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 41cef35..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,3910 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "Omikron" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "ansi_term", - "base64 0.22.1", - "dashmap", - "dotenv", - "futures", - "hex", - "hkdf", - "livekit-api", - "livekit-protocol", - "log", - "once_cell", - "rand 0.8.5", - "rand_core 0.6.4", - "rustls", - "sha2", - "strum", - "strum_macros", - "thiserror 2.0.18", - "tokio", - "ttp-core", - "ttp-native", - "uuid", - "x448", -] - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -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 = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "asn1-rs" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom", - "num-traits", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand", - "futures-lite", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-native-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" -dependencies = [ - "futures-util", - "native-tls", - "thiserror 1.0.69", - "url", -] - -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - -[[package]] -name = "async-tungstenite" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" -dependencies = [ - "async-native-tls", - "async-std", - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tungstenite 0.21.0", -] - -[[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.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" -dependencies = [ - "aws-lc-sys", - "untrusted 0.7.1", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.39.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[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.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "bitflags" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" - -[[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 = "block2" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" -dependencies = [ - "objc2", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite", - "piper", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cc" -version = "1.2.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "num-traits", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -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-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto 0.2.9", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" - -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom", - "num-bigint", - "num-traits", - "rusticata-macros", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", -] - -[[package]] -name = "dispatch2" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" -dependencies = [ - "bitflags", - "objc2", -] - -[[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 = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "elliptic-curve", - "rfc6979", - "signature", - "spki", -] - -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8", - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" -dependencies = [ - "curve25519-dalek", - "ed25519", - "serde", - "sha2", - "subtle", - "zeroize", -] - -[[package]] -name = "ed448-goldilocks" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" -dependencies = [ - "fiat-crypto 0.1.20", - "hex", - "subtle", -] - -[[package]] -name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest", - "ff", - "generic-array", - "group", - "hkdf", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[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.2", -] - -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - -[[package]] -name = "event-listener" -version = "5.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.1", - "pin-project-lite", -] - -[[package]] -name = "fastrand" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[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.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-executor" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "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", - "zeroize", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[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" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "httlib-huffman" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.4.0", -] - -[[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 1.4.0", - "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 = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "http 1.4.0", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[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.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http 1.4.0", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2 0.6.3", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[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.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" -dependencies = [ - "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", -] - -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "jsonwebtoken" -version = "10.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" -dependencies = [ - "base64 0.22.1", - "ed25519-dalek", - "getrandom 0.2.17", - "hmac", - "js-sys", - "p256", - "p384", - "rand 0.8.5", - "rsa", - "serde", - "serde_json", - "sha2", - "signature", -] - -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.184" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "livekit-api" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020c7eaab7cec16b69a058659f9acff32067e0e2d4f81e80639f3ffa29464b09" -dependencies = [ - "async-tungstenite", - "base64 0.21.7", - "http 1.4.0", - "jsonwebtoken", - "livekit-protocol", - "log", - "os_info", - "parking_lot", - "pbjson-types", - "prost", - "rand 0.9.2", - "reqwest", - "scopeguard", - "serde", - "serde_json", - "sha2", - "thiserror 1.0.69", - "tokio-tungstenite", - "url", -] - -[[package]] -name = "livekit-protocol" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c21d56b4dc2598b1296ca891abb225bf1c05ea007d6f43bc4fd48c31be64bf59" -dependencies = [ - "futures-util", - "livekit-runtime", - "parking_lot", - "pbjson", - "pbjson-types", - "prost", - "serde", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "livekit-runtime" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532e84c6cdc5fe774f2b5d9912597b5f3bea561927a48296d03e24549d21c3f6" -dependencies = [ - "tokio", - "tokio-stream", -] - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -dependencies = [ - "value-bag", -] - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multimap" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[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 = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.5", - "smallvec", - "zeroize", -] - -[[package]] -name = "num-conv" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "objc2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" -dependencies = [ - "objc2-encode", -] - -[[package]] -name = "objc2-cloud-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" -dependencies = [ - "bitflags", - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-data" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" -dependencies = [ - "bitflags", - "dispatch2", - "objc2", -] - -[[package]] -name = "objc2-core-graphics" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" -dependencies = [ - "bitflags", - "dispatch2", - "objc2", - "objc2-core-foundation", - "objc2-io-surface", -] - -[[package]] -name = "objc2-core-image" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-location" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "objc2-core-text" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", - "objc2-core-graphics", -] - -[[package]] -name = "objc2-encode" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" - -[[package]] -name = "objc2-foundation" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" -dependencies = [ - "bitflags", - "block2", - "libc", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-io-surface" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", -] - -[[package]] -name = "objc2-quartz-core" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" -dependencies = [ - "bitflags", - "objc2", - "objc2-core-foundation", - "objc2-foundation", -] - -[[package]] -name = "objc2-ui-kit" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" -dependencies = [ - "bitflags", - "block2", - "objc2", - "objc2-cloud-kit", - "objc2-core-data", - "objc2-core-foundation", - "objc2-core-graphics", - "objc2-core-image", - "objc2-core-location", - "objc2-core-text", - "objc2-foundation", - "objc2-quartz-core", - "objc2-user-notifications", -] - -[[package]] -name = "objc2-user-notifications" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" -dependencies = [ - "objc2", - "objc2-foundation", -] - -[[package]] -name = "octets" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" - -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "openssl" -version = "0.10.76" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" -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.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-sys" -version = "0.9.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "os_info" -version = "3.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" -dependencies = [ - "android_system_properties", - "log", - "nix", - "objc2", - "objc2-foundation", - "objc2-ui-kit", - "serde", - "windows-sys 0.61.2", -] - -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2", -] - -[[package]] -name = "parking" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "pbjson" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" -dependencies = [ - "base64 0.21.7", - "serde", -] - -[[package]] -name = "pbjson-build" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" -dependencies = [ - "heck 0.4.1", - "itertools 0.11.0", - "prost", - "prost-types", -] - -[[package]] -name = "pbjson-types" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" -dependencies = [ - "bytes", - "chrono", - "pbjson", - "pbjson-build", - "prost", - "prost-build", - "serde", -] - -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand", - "futures-io", -] - -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der", - "pkcs8", - "spki", -] - -[[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 = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[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 = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-build" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" -dependencies = [ - "bytes", - "heck 0.5.0", - "itertools 0.12.1", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost", - "prost-types", - "regex", - "syn", - "tempfile", -] - -[[package]] -name = "prost-derive" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "prost-types" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" -dependencies = [ - "prost", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.6.3", - "thiserror 2.0.18", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.2", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.18", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.6.3", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[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.5", -] - -[[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.5", -] - -[[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.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rcgen" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" -dependencies = [ - "aws-lc-rs", - "rustls-pki-types", - "time", - "x509-parser 0.18.1", - "yasna", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "regex" -version = "1.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-core", - "http 1.4.0", - "http-body", - "http-body-util", - "hyper", - "hyper-tls", - "hyper-util", - "js-sys", - "log", - "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 = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[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.17", - "libc", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core 0.6.4", - "signature", - "spki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted 0.9.0", -] - -[[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.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" - -[[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.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[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 = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[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.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "syn", -] - -[[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.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -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 = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.2", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.3.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde_core", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" - -[[package]] -name = "time-macros" -version = "0.2.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2 0.6.3", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" -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-stream" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite 0.20.1", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http 1.4.0", - "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.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "ttp-core" -version = "0.1.0" -source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" -dependencies = [ - "base64 0.22.1", - "byteorder", - "rand 0.8.5", - "strum", - "strum_macros", -] - -[[package]] -name = "ttp-native" -version = "0.1.0" -source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" -dependencies = [ - "quinn", - "rustls", - "rustls-native-certs", - "thiserror 2.0.18", - "tokio", - "ttp-core", - "wtransport", -] - -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 0.2.12", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", -] - -[[package]] -name = "tungstenite" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.4.0", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1", - "thiserror 1.0.69", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[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.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -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.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" - -[[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 = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "web-sys" -version = "0.3.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -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-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[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_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" - -[[package]] -name = "wtransport" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" -dependencies = [ - "bytes", - "pem", - "quinn", - "rcgen", - "rustls", - "rustls-native-certs", - "rustls-pki-types", - "sha2", - "socket2 0.5.10", - "thiserror 2.0.18", - "time", - "tokio", - "tracing", - "url", - "wtransport-proto", - "x509-parser 0.17.0", -] - -[[package]] -name = "wtransport-proto" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" -dependencies = [ - "httlib-huffman", - "octets", - "thiserror 2.0.18", - "url", -] - -[[package]] -name = "x448" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" -dependencies = [ - "ed448-goldilocks", - "hex", - "rand_core 0.5.1", -] - -[[package]] -name = "x509-parser" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "x509-parser" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" -dependencies = [ - "asn1-rs", - "aws-lc-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -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.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 3127b12..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,37 +0,0 @@ -[package] -name = "Omikron" -version = "0.1.0" -edition = "2024" - -[dependencies] -ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" } -ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" } - -ansi_term = "*" -uuid = { version = "*", features = ["v4"] } - -base64 = "0.22.1" -dashmap = "*" -futures = "*" -hex = "*" -once_cell = "1.21.3" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -rustls = { version = "0.23.37", default-features = false, features = [ - "std", - "tls12", - "aws-lc-rs", - "prefer-post-quantum", -] } -aes-gcm = "0.10.3" -sha2 = "0.10.9" -tokio = { version = "*", features = ["full"] } -x448 = { version = "*" } -log = "0.4" -dotenv = "0.15.0" -hkdf = "0.12.4" -strum = "0.28.0" -strum_macros = "0.28.0" -livekit-api = { version = "0.4.14", features = ["native-tls"] } -livekit-protocol = "0.7.1" -thiserror = "2.0.18" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d218eff..0000000 --- a/LICENSE +++ /dev/null @@ -1,16 +0,0 @@ -Copyright (c) [2025] [Methanium] -All rights reserved. - -This software is protected by copyright. Copying, editing, -distributing, publicly performing, or any other use of this software -or its components, in source or binary form, is strictly prohibited without the express -written permission of the copyright holder. - -FUTURE LICENSE ACCEPTANCE: -It is the copyright holder's intention to release this software in the future -under a license yet to be defined, which will, among other things, -allow private, non-commercial use. This statement does not constitute -a current license grant and does not alter the above -prohibition on use, copying, or modification. Until the formal -publication of such a future license, all rights remain -reserved. diff --git a/README.md b/README.md deleted file mode 100644 index 06c1cdb..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omikron) ⚠️ diff --git a/src/anonymous_clients/anonymous_client_connection.rs b/src/anonymous_clients/anonymous_client_connection.rs deleted file mode 100644 index 8c57490..0000000 --- a/src/anonymous_clients/anonymous_client_connection.rs +++ /dev/null @@ -1,519 +0,0 @@ -use std::str::FromStr; -use std::sync::Arc; -use std::time::Duration; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Receiver, Sender}; -use uuid::Uuid; - -use crate::anonymous_clients::anonymous_manager::{self, generate_username}; -use crate::calls::call_manager; -use crate::omega::omega_connection::get_omega_connection; -use crate::rho::connection::GeneralConnection; -use crate::rho::rho_manager; -use crate::util::logger::PrintType; -use crate::{log_cv_in, log_cv_out, log_out}; - -pub struct AnonymousClientConnection { - user_id: u64, - - pub sender: Arc, - pub receiver: Arc, - pub ping: Arc>, - pub interested_users: Arc>>, - is_open: Arc>, - pub user_name: Arc>, - pub display_name: Arc>, - pub avatar: Arc>, -} - -impl AnonymousClientConnection { - pub async fn from_general(general: Arc, user_id: u64) -> Arc { - let username: String = generate_username(); - Arc::new(Self { - user_id: user_id, - - ping: Arc::new(RwLock::new(0)), - interested_users: Arc::new(RwLock::new(Vec::new())), - is_open: Arc::new(RwLock::new(true)), - sender: general.sender.clone(), - receiver: general.receiver.clone(), - user_name: Arc::new(RwLock::new(username.to_lowercase())), - display_name: Arc::new(RwLock::new(username)), - avatar: Arc::new(RwLock::new(String::new())), - }) - } - pub fn start(self: Arc) { - let self_clone = self.clone(); - tokio::spawn(async move { - while let Ok(cv) = self_clone.receiver.receive().await { - self_clone.clone().handle_message(cv).await; - } - self_clone.handle_close().await; - }); - } - - /// Get the user ID - pub fn get_user_id(&self) -> u64 { - self.user_id - } - - /// Get the user name - pub async fn get_user_name(&self) -> String { - self.user_name.read().await.clone() - } - - /// Get the display name - pub async fn get_display_name(&self) -> String { - self.display_name.read().await.clone() - } - - pub async fn set_display_name(&self, display: String) { - *self.display_name.write().await = display; - } - - /// Get the avatar - pub async fn get_avatar(&self) -> String { - self.avatar.read().await.clone() - } - - /// Send a CommunicationValue to the client - pub async fn send_message(self: Arc, cv: &CommunicationValue) { - if !*self.is_open.read().await { - log_out!( - self.user_id as i64, - PrintType::Client, - "Attempted to send message to a closed connection." - ); - return; - } - if !cv.is_type(CommunicationType::pong) { - log_cv_out!(PrintType::Client, &cv); - } - if let Err(e) = self.sender.send(&cv).await { - log_out!( - self.user_id as i64, - PrintType::Client, - "Send failed: {:?}", - e - ); - } - } - - /// Handle incoming message from client - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - tokio::spawn(async move { - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; - } - log_cv_in!(PrintType::Client, &cv); - - if cv.is_type(CommunicationType::identification) { - let call_id = - Uuid::parse_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")) - .unwrap_or(Uuid::new_v4()); - - let call = if let Some(call) = call_manager::get_call(call_id).await { - if call.is_anonymous().await { - call - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_not_authenticated, - ) - .await; - return; - } - } else { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_not_authenticated, - ) - .await; - return; - }; - - let mut invited = Vec::new(); - for call_invitee in call.members.read().await.clone() { - let call_invitee_cv = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::get_user_data).add_data( - DataTypes::user_id, - DataValue::Number(call_invitee.user_id as i64), - ), - Some(Duration::from_secs(2)), - ) - .await - .unwrap(); - let mut json_invitee = Vec::new(); - let _ = json_invitee.push(( - DataTypes::user_id, - call_invitee_cv.get_data(DataTypes::user_id).clone(), - )); - let _ = json_invitee.push(( - DataTypes::username, - call_invitee_cv.get_data(DataTypes::username).clone(), - )); - let _ = json_invitee.push(( - DataTypes::display, - call_invitee_cv.get_data(DataTypes::display).clone(), - )); - let _ = json_invitee.push(( - DataTypes::avatar, - call_invitee_cv.get_data(DataTypes::avatar).clone(), - )); - - let _ = invited.push(DataValue::Container(json_invitee)); - } - - let token = call.create_anonymous_token(self.get_user_id()).await; - - let mut serialized = Vec::new(); - let _ = serialized.push((DataTypes::call_id, DataValue::Str(call_id.to_string()))); - let _ = - serialized.push((DataTypes::call_invited, DataValue::Array(invited.clone()))); - let _ = serialized.push((DataTypes::call_members, DataValue::Array(invited))); - let _ = serialized.push((DataTypes::call_token, DataValue::Str(token.unwrap()))); - self.clone() - .send_message( - &&CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(self.user_id as i64)) - .add_data( - DataTypes::username, - DataValue::Str(self.clone().get_user_name().await), - ) - .add_data( - DataTypes::display, - DataValue::Str(self.get_display_name().await), - ) - .add_data(DataTypes::avatar, DataValue::Str(self.get_avatar().await)) - .add_data(DataTypes::call_state, DataValue::Container(serialized)), - ) - .await; - } - - // 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::call_token) { - self.handle_get_call(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_disconnect_user) { - self.handle_call_disconnect_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_timeout_user) { - self.handle_call_timeout_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::change_user_data) { - if let Some(display_name) = cv.get_data(DataTypes::display).as_str() { - let _ = self.set_display_name(display_name.to_string()).await; - } - - return; - } - - if cv.is_type(CommunicationType::get_user_data) { - if let Some(anonymous) = { - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - anonymous_manager::get_anonymous_user(user_id as u64).await - } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { - anonymous_manager::get_anonymous_user_by_name(username.to_string()).await - } else { - None - } - } { - let response = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data( - DataTypes::username, - DataValue::Str(anonymous.get_user_name().await), - ) - .add_data( - DataTypes::user_id, - DataValue::Number(anonymous.user_id as i64), - ) - .add_data( - DataTypes::display, - DataValue::Str(anonymous.get_display_name().await), - ) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())) - .add_data( - DataTypes::avatar, - DataValue::Str(anonymous.get_avatar().await), - ); - - self.send_message(&response).await; - - return; - } - } - - if cv.is_type(CommunicationType::get_user_data) - || cv.is_type(CommunicationType::get_iota_data) - || cv.is_type(CommunicationType::delete_user) - { - self.handle_omega_forward(cv).await; - return; - } - }); - } - async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { - let client_for_closure = self.clone(); - tokio::spawn(async move { - let response_cv = get_omega_connection() - .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) - .await; - if let Ok(response_cv) = response_cv { - client_for_closure.send_message(&response_cv).await; - } - }); - } - - /// Handle ping message - async fn handle_ping(self: Arc, cv: CommunicationValue) { - // Update our ping if provided - if let DataValue::Number(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; - } - } - - // Send pong response - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); - - self.send_message(&response).await; - } - - /// Handle client status change - async fn handle_client_changed(self: Arc, _cv: CommunicationValue) { - /*let user_id = self.get_user_id().await; - if let Some(_status_str) = cv.get_data(DataTypes::user_state) { - let user_status = UserStatus::online; - }*/ - } - - /// Handle call invite - async fn handle_call_invite(self: Arc, cv: CommunicationValue) { - let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); - if receiver_id == 0 { - self.send_error_response(&cv.get_id(), CommunicationType::error_no_user_id) - .await; - return; - } - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(id_str) => match Uuid::parse_str(&id_str.to_string()) { - Ok(id) => id, - Err(_) => { - self.send_error_response( - &cv.get_id(), - CommunicationType::error_invalid_call_id, - ) - .await; - return; - } - }, - _ => { - self.send_error_response(&cv.get_id(), CommunicationType::error_no_call_id) - .await; - return; - } - }; - - let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; - if !invited { - self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_call_id) - .await; - return; - } - - // Find target RhoConnection - let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { - Some(rho) => rho, - _ => { - self.send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - // Get sender user ID - let sender_id = self.get_user_id(); - - // Create and send call distribution message - let forward = CommunicationValue::new(CommunicationType::call_invite) - .with_receiver(receiver_id as u64) - .with_sender(sender_id) - .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) - .add_data( - DataTypes::receiver_id, - DataValue::Str(receiver_id.to_string()), - ) - .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); - - target_rho.message_to_client(forward).await; - - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } - - /// Handle get call request - async fn handle_get_call(self: Arc, cv: CommunicationValue) { - let user_id = self.get_user_id(); - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(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; - } - }, - _ => { - self.send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - if let Some(token) = call_manager::get_call_token(user_id, call_id).await { - let response = CommunicationValue::new(CommunicationType::call_token) - .with_id(cv.get_id()) - .with_receiver(user_id) - .add_data(DataTypes::call_token, DataValue::Str(token.to_string())); - self.send_message(&response).await; - } else { - self.send_error_response(&cv.get_id(), CommunicationType::error) - .await; - return; - } - } - async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id()) - .await - .unwrap() - .has_admin() - { - call.get_caller(user_id as u64) - .await - .unwrap() - .set_timeout(untill) - .await; - } - } - } - async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id()) - .await - .unwrap() - .has_admin() - { - call.remove_caller(user_id as u64).await; - } - } - } - - /// Send error response - async fn send_error_response(self: Arc, message_id: &u32, 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 is_open_guard = self.is_open.write().await; - if !*is_open_guard { - return; - } - *is_open_guard = false; - - let _ = self.sender.close(); - } - - #[allow(dead_code)] - /// Set interested users list - pub async fn set_interested_users(self: Arc, interested_ids: Vec) { - let mut interested_guard = self.interested_users.write().await; - *interested_guard = interested_ids; - } - #[allow(dead_code)] - pub async fn get_interested_users(self: Arc) -> Vec { - let interested_guard = self.interested_users.read().await; - interested_guard.clone() - } - - #[allow(dead_code)] - /// Check if interested in a user and send notification - pub async fn are_you_interested(self: Arc, user_id: i64) { - let interested_guard = self.clone().get_interested_users().await; - if interested_guard.contains(&user_id) { - let notification = CommunicationValue::new(CommunicationType::client_changed) - .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); - - self.send_message(¬ification).await; - } - } - - /// Handle connection close - pub async fn handle_close(&self) { - - // TODO delete temp user - } -} - -// Implement Clone to make it easier to work with Arc -impl Clone for AnonymousClientConnection { - fn clone(&self) -> Self { - Self { - sender: Arc::clone(&self.sender), - receiver: Arc::clone(&self.receiver), - user_id: self.user_id, - ping: Arc::clone(&self.ping), - interested_users: Arc::clone(&self.interested_users), - is_open: Arc::clone(&self.is_open), - user_name: Arc::clone(&self.user_name), - display_name: Arc::clone(&self.display_name), - avatar: Arc::clone(&self.avatar), - } - } -} diff --git a/src/anonymous_clients/anonymous_manager.rs b/src/anonymous_clients/anonymous_manager.rs deleted file mode 100644 index edfefde..0000000 --- a/src/anonymous_clients/anonymous_manager.rs +++ /dev/null @@ -1,53 +0,0 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::Rng; -use rand::seq::SliceRandom; -use std::sync::Arc; - -use crate::anonymous_clients::anonymous_client_connection::AnonymousClientConnection; - -static ANONYMOUS_USERS: Lazy>> = - Lazy::new(|| DashMap::new()); - -#[allow(dead_code)] -pub async fn add_anonymous_user(connection: Arc) { - ANONYMOUS_USERS.insert(connection.get_user_id(), connection); -} - -#[allow(dead_code)] -pub async fn remove_anonymous_user(user_id: u64) { - ANONYMOUS_USERS.remove(&user_id); -} - -pub async fn get_anonymous_user(user_id: u64) -> Option> { - ANONYMOUS_USERS.get(&user_id).map(|c| c.clone()) -} - -pub async fn get_anonymous_user_by_name( - username: String, -) -> Option> { - for user_conn in ANONYMOUS_USERS - .iter() - .map(|ref_multi| ref_multi.value().clone()) - { - if user_conn.get_user_name().await == username { - return Some(user_conn); - } - } - - return None; -} - -// TODO: implement check if taken -pub fn generate_username() -> String { - let adjectives = ["Swift", "Clever", "Brave", "Sneaky", "Fierce"]; - let nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Dragon"]; - - let mut rng = rand::thread_rng(); - let adj = adjectives.choose(&mut rng).unwrap(); - let noun = nouns.choose(&mut rng).unwrap(); - - let number: u16 = rng.gen_range(0..10000); - - format!("{}{}{}", adj, noun, number) -} diff --git a/src/anonymous_clients/mod.rs b/src/anonymous_clients/mod.rs deleted file mode 100644 index 3edd0b4..0000000 --- a/src/anonymous_clients/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod anonymous_client_connection; -pub mod anonymous_manager; diff --git a/src/calls/call_group.rs b/src/calls/call_group.rs deleted file mode 100644 index 91e85cf..0000000 --- a/src/calls/call_group.rs +++ /dev/null @@ -1,106 +0,0 @@ -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; - -use std::{env, sync::Arc, time::Duration}; -use tokio::sync::RwLock; -use uuid::Uuid; - -use crate::{ - calls::{call_util, caller::Caller}, - omega::omega_connection::get_omega_connection, -}; - -pub struct CallGroup { - pub call_id: Uuid, - pub members: RwLock>>, - pub show: RwLock, - pub anonymous_joining: RwLock, - pub short_link: RwLock>, -} - -impl CallGroup { - pub fn new(call_id: Uuid, user: Arc) -> Self { - CallGroup { - call_id, - members: RwLock::new(vec![user]), - show: RwLock::new(true), - anonymous_joining: RwLock::new(false), - short_link: RwLock::new(None), - } - } - - pub async fn get_caller(&self, user_id: u64) -> Option> { - self.members - .read() - .await - .iter() - .find(|caller| caller.user_id == user_id) - .cloned() - } - - pub async fn is_anonymous(&self) -> bool { - *self.anonymous_joining.read().await - } - - pub async fn set_anonymous_joining(&self, enable: bool) { - *self.anonymous_joining.write().await = enable; - - let _ = call_util::set_room_metadata( - self.call_id, - format!("{{\"anonymous_joining\": \"{}\"}}", enable), - ) - .await; - - if self.short_link.read().await.is_none() { - let long_link = format!( - "https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}", - self.call_id, - env::var("ID") - .unwrap_or("0".to_string()) - .parse::() - .unwrap_or(0), - ); - let response_cv = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::shorten_link) - .add_data(DataTypes::link, DataValue::Str(long_link)), - Some(Duration::from_secs(20)), - ) - .await; - if let Ok(response) = response_cv { - *self.short_link.write().await = Some( - response - .get_data(DataTypes::link) - .as_str() - .unwrap() - .to_string(), - ); - log::info!( - "Shortened link for call {} is {}", - self.call_id, - self.short_link.read().await.as_ref().unwrap() - ); - } - } - } - - pub async fn create_anonymous_token(&self, user_id: u64) -> Option { - if self.is_anonymous().await { - if let Ok(token) = call_util::create_token(user_id, self.call_id, false) { - return Some(token); - } - } - None - } - - pub async fn remove_caller(&self, user_id: u64) { - let _ = call_util::remove_participant(self.call_id, user_id).await; - self.members - .write() - .await - .retain(|caller| caller.user_id != user_id); - } - - pub async fn get_short_link(self: Arc) -> Option { - self.short_link.read().await.clone() - } -} diff --git a/src/calls/call_manager.rs b/src/calls/call_manager.rs deleted file mode 100644 index a17f347..0000000 --- a/src/calls/call_manager.rs +++ /dev/null @@ -1,97 +0,0 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use std::sync::Arc; -use uuid::Uuid; - -use crate::calls::{call_group::CallGroup, caller::Caller}; - -pub static CALL_GROUPS: Lazy>> = Lazy::new(|| DashMap::new()); -#[allow(dead_code)] -pub async fn get_call_invites(user_id: u64) -> Vec> { - let mut callers = Vec::new(); - for (_, cg) in CALL_GROUPS.clone().into_iter() { - let members = cg.members.read().await; - for member in members.iter() { - if member.user_id == user_id { - callers.push(member.clone()); - } - } - } - callers -} - -pub async fn get_call(call_id: Uuid) -> Option> { - if let Some(b) = CALL_GROUPS.get(&call_id) { - Some(b.clone()) - } else { - None - } -} - -pub async fn get_call_groups(user_id: u64) -> Vec> { - let mut call_groups = Vec::new(); - for (_, cg) in CALL_GROUPS.clone().into_iter() { - let is_member = { - let members = cg.members.read().await; - members.iter().any(|m| m.user_id == user_id) - }; - - if is_member { - call_groups.push(cg.clone()); - } - } - call_groups -} - -pub async fn get_call_token(user_id: u64, call_id: Uuid) -> Option { - if let Some(cg) = CALL_GROUPS.get(&call_id) { - let mut members = cg.members.write().await; - - if let Some(member) = members.iter().find(|m| m.user_id == user_id) { - return Some(member.create_token()); - } - - let new_caller = Arc::new(Caller::new(user_id, call_id, false)); - let token = new_caller.create_token(); - - members.push(new_caller); - - return Some(token); - } - - if let Some(cg) = CALL_GROUPS.get(&call_id) { - let cg_clone = cg.clone(); - - let mut members = cg_clone.members.write().await; - if let Some(member) = members.iter().find(|m| m.user_id == user_id) { - return Some(member.create_token()); - } - let new_caller = Arc::new(Caller::new(user_id, call_id, false)); - let token = new_caller.create_token(); - members.push(new_caller); - return Some(token); - } - - let caller = Arc::new(Caller::new(user_id, call_id, true)); - let call_group = CallGroup::new(call_id, caller.clone()); - - CALL_GROUPS.insert(call_id, Arc::new(call_group)); - - Some(caller.create_token()) -} - -pub async fn add_invite(call_id: Uuid, inviter_id: u64, invitee_id: u64) -> bool { - if let Some(cg) = CALL_GROUPS.get(&call_id) { - let mut members = cg.members.write().await; - - let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id); - - if is_inviter_member { - if !members.iter().any(|m| m.user_id == invitee_id) { - members.push(Arc::new(Caller::new(invitee_id, call_id, false))); - } - return true; - } - } - false -} diff --git a/src/calls/call_util.rs b/src/calls/call_util.rs deleted file mode 100644 index 57df005..0000000 --- a/src/calls/call_util.rs +++ /dev/null @@ -1,151 +0,0 @@ -use livekit_api::{ - access_token::{self}, - services::room::RoomClient, -}; -use livekit_protocol::Room; -use std::env; -use std::str::FromStr; -use std::time::Duration; -use uuid::Uuid; - -use crate::{calls::call_manager::CALL_GROUPS, log, log_err, util::logger::PrintType}; - -pub fn get_livekit() -> Result<(String, String, String), ()> { - let hostname = match env::var("LIVEKI_HOSTNAME") { - Ok(secret) => secret, - Err(_) => { - log_err!(0, PrintType::General, "LIVEKI_HOSTNAME not set!"); - return Err(()); - } - }; - let api_key = match env::var("LIVEKIT_API_KEY") { - Ok(key) => key, - Err(_) => { - log_err!(0, PrintType::General, "LIVEKIT_API_KEY not set!"); - return Err(()); - } - }; - let api_secret = match env::var("LIVEKIT_API_SECRET") { - Ok(secret) => secret, - Err(_) => { - log_err!(0, PrintType::General, "LIVEKIT_API_SECRET not set!"); - return Err(()); - } - }; - Ok((hostname, api_key, api_secret)) -} - -pub fn create_token(user_id: u64, call_id: Uuid, has_admin: bool) -> Result { - let (_, api_key, api_secret) = get_livekit()?; - - let token = access_token::AccessToken::with_api_key(&api_key, &api_secret) - .with_identity(&user_id.to_string()) - .with_grants(access_token::VideoGrants { - room_join: true, - room_admin: has_admin, - room: call_id.to_string(), - ..Default::default() - }) - .with_metadata(&format!("{{\"isAdmin\":{}}}", has_admin)) - .to_jwt(); - if let Ok(token) = token { - Ok(token) - } else { - Err(()) - } -} -#[allow(dead_code)] -pub async fn get_room(call_id: Uuid) -> Result<(RoomClient, Room), ()> { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - let rooms = room_service.list_rooms(Vec::new()).await; - if let Ok(rooms) = rooms { - for room in rooms { - if room.name == call_id.to_string() { - return Ok((room_service, room)); - } - } - } - } - return Err(()); -} - -pub async fn remove_participant(call_id: Uuid, user_id: u64) -> Result<(), ()> { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - if let Ok(_) = room_service - .remove_participant(&call_id.to_string(), &user_id.to_string()) - .await - { - return Ok(()); - } - } - return Err(()); -} - -#[allow(dead_code)] -pub async fn get_room_metadata(call_id: Uuid) -> Result { - if let Ok((_, room)) = get_room(call_id).await { - Ok(room.metadata) - } else { - Err(()) - } -} - -pub async fn set_room_metadata(call_id: Uuid, metadata: String) -> Result<(), ()> { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - if let Ok(_) = room_service - .update_room_metadata(&call_id.to_string(), &metadata) - .await - { - return Ok(()); - } - } - return Err(()); -} - -pub fn garbage_collect_calls() { - tokio::spawn(async move { - loop { - if let Ok((hostname, api_key, api_secret)) = get_livekit() { - let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); - clean_calls(room_service).await; - } - tokio::time::sleep(Duration::from_secs(2)).await; - } - }); -} -pub async fn clean_calls(room_service: RoomClient) { - let rooms = room_service.list_rooms(Vec::new()).await.unwrap(); - let mut call_ids: Vec = Vec::new(); - let mut no_users: Vec = Vec::new(); - for room in rooms { - if let Ok(id) = Uuid::from_str(&room.name) { - if room.num_participants == 0 { - no_users.push(id); - } - call_ids.push(id); - } - } - let size_pre = CALL_GROUPS.len(); - for (id, _) in CALL_GROUPS.clone().into_iter() { - if !call_ids.contains(&id) { - CALL_GROUPS.remove(&id); - } - } - for (_, cg) in CALL_GROUPS.clone().into_iter() { - *cg.show.write().await = !no_users.contains(&cg.call_id); - } - - let size_post = CALL_GROUPS.len(); - if size_pre - size_post != 0 { - log!( - 0, - PrintType::Call, - "Cleaned {} calls, {} remaining", - size_pre - size_post, - size_post - ); - } -} diff --git a/src/calls/caller.rs b/src/calls/caller.rs deleted file mode 100644 index 74e0c3b..0000000 --- a/src/calls/caller.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::time::{SystemTime, UNIX_EPOCH}; - -use tokio::sync::RwLock; -use uuid::Uuid; - -use crate::calls::call_util; - -pub struct Caller { - pub user_id: u64, - pub call_id: Uuid, - pub has_admin: bool, - pub timeout: RwLock, -} - -impl Caller { - pub fn new(user_id: u64, call_id: Uuid, has_admin: bool) -> Self { - Caller { - user_id, - call_id, - has_admin, - timeout: RwLock::new(0), - } - } - #[allow(dead_code)] - pub fn set_admin(&mut self, has_admin: bool) { - self.has_admin = has_admin; - } - pub fn has_admin(&self) -> bool { - self.has_admin - } - #[allow(dead_code)] - pub async fn is_timeouted(&self) -> bool { - *self.timeout.read().await - > SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as i64 - } - pub async fn set_timeout(&self, timeout: i64) { - *self.timeout.write().await = timeout; - } - pub fn create_token(&self) -> String { - if let Ok(token) = call_util::create_token(self.user_id, self.call_id, self.has_admin()) { - token - } else { - String::new() - } - } -} diff --git a/src/calls/mod.rs b/src/calls/mod.rs deleted file mode 100644 index 8413354..0000000 --- a/src/calls/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod call_group; -pub mod call_manager; -pub mod call_util; -pub mod caller; diff --git a/src/data/mod.rs b/src/data/mod.rs deleted file mode 100644 index 22d12a3..0000000 --- a/src/data/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod user; diff --git a/src/data/user.rs b/src/data/user.rs deleted file mode 100644 index a175e6d..0000000 --- a/src/data/user.rs +++ /dev/null @@ -1,30 +0,0 @@ -use strum::IntoEnumIterator; -use strum_macros::EnumIter; - -#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] -#[allow(unused, non_camel_case_types)] -pub enum UserStatus { - user_offline, - user_online, - user_dnd, - user_idle, - user_wc, - user_borked, - iota_offline, - iota_online, - iota_borked, -} -#[allow(unused)] -impl UserStatus { - pub fn to_string(&self) -> String { - format!("{:?}", self) - } - pub fn from_str(s: &str) -> Option { - for sel in UserStatus::iter() { - if &sel.to_string() == s { - return Some(sel); - } - } - None - } -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index fa511ea..0000000 --- a/src/main.rs +++ /dev/null @@ -1,51 +0,0 @@ -mod anonymous_clients; -mod calls; -mod data; -mod omega; -mod rho; -mod util; - -use std::env; - -use dotenv::dotenv; -use once_cell::sync::Lazy; -use rustls::crypto::aws_lc_rs::default_provider; - -use crate::{ - calls::call_util::garbage_collect_calls, - omega::omega_connection::get_omega_connection, - rho::server::start, - util::{ - crypto_helper::{load_public_key, load_secret_key}, - logger::startup, - }, -}; - -static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); -pub fn get_private_key() -> x448::Secret { - load_secret_key(&*PRIVATE_KEY).unwrap() -} -static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); -pub fn get_public_key() -> x448::PublicKey { - load_public_key(&*PUBLIC_KEY).unwrap() -} - -#[tokio::main] -async fn main() { - if let Err(_) = default_provider().install_default() { - println!("Error loading Provider"); - return; - } - dotenv().ok(); - startup(); - - get_omega_connection(); - tokio::spawn(async move { - if let Err(e) = start(959).await { - log_err!(0, util::logger::PrintType::General, "{}", e); - } - }); - garbage_collect_calls(); - - tokio::signal::ctrl_c().await.unwrap(); -} diff --git a/src/omega/mod.rs b/src/omega/mod.rs deleted file mode 100644 index cbe77d9..0000000 --- a/src/omega/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod omega_connection; diff --git a/src/omega/omega_connection.rs b/src/omega/omega_connection.rs deleted file mode 100644 index ab84eb2..0000000 --- a/src/omega/omega_connection.rs +++ /dev/null @@ -1,741 +0,0 @@ -use crate::{ - data::user::UserStatus, - get_private_key, log, log_cv_in, log_cv_out, log_err, log_in, - rho::rho_manager::{self, RHO_CONNECTIONS, connection_count}, - util::{ - crypto_helper::{decrypt_b64, secret_key_to_base64}, - file_util::load_file_vec, - logger::PrintType, - }, -}; -use dashmap::DashMap; -use once_cell::sync::Lazy; -use std::{collections::HashMap, env, sync::Arc, time::Duration}; -use tokio::{ - sync::{Mutex, RwLock, mpsc, watch}, - task::JoinHandle, - time::{Instant, sleep}, -}; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; -use ttp_native::{Receiver, Sender}; -use uuid::Uuid; - -// ============================================================================ -// Configuration -// ============================================================================ - -const OMEGA_HOST_DEFAULT: &str = "methanium.net"; -const OMEGA_PORT_DEFAULT: u16 = 9187; -const RECONNECT_DELAY: Duration = Duration::from_secs(5); -const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); -const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); -const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); -const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); -const TASK_MAX_AGE: Duration = Duration::from_secs(60); - -// ============================================================================ -// Waiting Task System -// ============================================================================ - -pub struct WaitingTask { - pub task: Box, CommunicationValue) -> bool + Send + Sync>, - pub inserted_at: Instant, -} - -pub static WAITING_TASKS: Lazy> = Lazy::new(DashMap::new); - -pub fn start_task_cleanup_loop() { - tokio::spawn(async { - loop { - sleep(TASK_CLEANUP_INTERVAL).await; - WAITING_TASKS.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE); - } - }); -} - -// ============================================================================ -// Connection State -// ============================================================================ - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum ConnectionState { - Disconnected, - Connecting, - Connected { identified: bool }, -} - -#[allow(unused_variables)] -impl ConnectionState { - pub fn is_connected(&self) -> bool { - match self { - ConnectionState::Connected { identified } => true, - _ => false, - } - } - - #[allow(dead_code)] - pub fn is_identified(&self) -> bool { - match self { - ConnectionState::Connected { identified: true } => true, - _ => false, - } - } -} - -// ============================================================================ -// Omega Connection (Client-side with auto-reconnect) -// ============================================================================ -#[allow(dead_code)] -pub struct OmegaConnection { - state: Arc>, - sender: Arc>>>, - connection_loop_handle: Arc>>>, - host: String, - port: u16, - server_cert: Vec, - last_ping: Arc>, - heartbeat_handle: Arc>>>, - message_send_times: Arc>>, - pub connection_id: Uuid, - shutdown_tx: Arc>>>, - // Track if we should reconnect on close - reconnect_on_close: Arc>, -} - -impl OmegaConnection { - pub fn new() -> Self { - Self::with_host(OMEGA_HOST_DEFAULT, OMEGA_PORT_DEFAULT) - } - - pub fn with_host(host: &str, port: u16) -> Self { - // Load server certificate from default location - let server_cert = - load_file_vec("certs", "cert.pem").expect("Failed to load server certificate"); - - Self::with_host_and_cert(host, port, server_cert) - } - - // New constructor that accepts certificate directly - pub fn with_host_and_cert(host: &str, port: u16, server_cert: Vec) -> Self { - let (shutdown_tx, _) = watch::channel(false); - - OmegaConnection { - state: Arc::new(RwLock::new(ConnectionState::Disconnected)), - sender: Arc::new(RwLock::new(None)), - connection_loop_handle: Arc::new(Mutex::new(None)), - host: host.to_string(), - port, - server_cert, - last_ping: Arc::new(Mutex::new(-1)), - heartbeat_handle: Arc::new(Mutex::new(None)), - message_send_times: Arc::new(Mutex::new(HashMap::new())), - connection_id: Uuid::new_v4(), - shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), - reconnect_on_close: Arc::new(RwLock::new(true)), - } - } - - // ------------------------------------------------------------------------- - // Connection Management - // ------------------------------------------------------------------------- - - pub async fn start(self: Arc) { - // Cancel any existing connection loop - if let Some(handle) = self.connection_loop_handle.lock().await.take() { - handle.abort(); - } - - // Set reconnect flag - *self.reconnect_on_close.write().await = true; - - let self_clone = self.clone(); - let handle = tokio::spawn(async move { - self_clone.connection_loop().await; - }); - - *self.connection_loop_handle.lock().await = Some(handle); - } - - #[allow(dead_code)] - pub async fn stop(&self) { - // Disable reconnection - *self.reconnect_on_close.write().await = false; - - if let Some(tx) = self.shutdown_tx.lock().await.take() { - let _ = tx.send(true); - } - - if let Some(handle) = self.connection_loop_handle.lock().await.take() { - handle.abort(); - } - - if let Some(handle) = self.heartbeat_handle.lock().await.take() { - handle.abort(); - } - - // Close sender if connected - if let Some(sender) = self.sender.read().await.as_ref() { - sender.close(); - } - - *self.state.write().await = ConnectionState::Disconnected; - *self.sender.write().await = None; - } - - async fn connection_loop(self: Arc) { - let mut reconnect_delay = RECONNECT_DELAY; - let shutdown_rx = self.shutdown_tx.lock().await.as_ref().unwrap().subscribe(); - let mut shutdown_rx = shutdown_rx; - - loop { - if *shutdown_rx.borrow() { - log_in!(0, PrintType::Omega, "Connection loop shutting down"); - break; - } - - // Check if reconnection is enabled - if !*self.reconnect_on_close.read().await { - log_in!(0, PrintType::Omega, "Reconnection disabled, exiting loop"); - break; - } - - match self.clone().connect_once().await { - Ok(()) => { - // Connection closed gracefully, check if we should reconnect - if *self.reconnect_on_close.read().await { - log_err!( - 0, - PrintType::Omega, - "Connection lost, reconnecting in {:?}...", - reconnect_delay - ); - } else { - log_in!(0, PrintType::Omega, "Connection closed, not reconnecting"); - break; - } - } - Err(e) => { - log_err!( - 0, - PrintType::Omega, - "Connection failed: {}, retrying in {:?}...", - e, - reconnect_delay - ); - } - } - - tokio::select! { - _ = sleep(reconnect_delay) => {} - _ = shutdown_rx.changed() => { - if *shutdown_rx.borrow() { - break; - } - } - } - - reconnect_delay = std::cmp::min(reconnect_delay * 2, MAX_RECONNECT_DELAY); - } - } - - async fn connect_once(self: Arc) -> Result<(), String> { - *self.state.write().await = ConnectionState::Connecting; - - let addr_str = format!("https://{}:{}", self.host, self.port); - - let (sender, mut receiver) = ttp_native::client::connect(&addr_str, None) - .await - .map_err(|e| format!("Connection failed: {}", e))?; - - log_in!( - 0, - PrintType::Omega, - "QUIC connection established to {}", - addr_str - ); - - // Store sender - let sender_arc = Arc::new(sender); - *self.sender.write().await = Some(sender_arc.clone()); - *self.state.write().await = ConnectionState::Connected { identified: false }; - - // Get handle for close monitoring - let sender_handle = sender_arc.handle().clone(); - - // Start read loop - let read_self = self.clone(); - let read_handle = tokio::spawn(async move { - read_self.read_loop(&mut receiver, sender_handle).await; - }); - - // Send identification - self.send_identification().await; - - // Start heartbeat - let heartbeat_self = self.clone(); - let heartbeat_handle = tokio::spawn(async move { - heartbeat_self.heartbeat_loop().await; - }); - *self.heartbeat_handle.lock().await = Some(heartbeat_handle); - - // Wait for read loop to complete (connection closed) - let result = read_handle.await; - - // Cleanup - *self.sender.write().await = None; - *self.state.write().await = ConnectionState::Disconnected; - - if let Some(handle) = self.heartbeat_handle.lock().await.take() { - handle.abort(); - } - - match result { - Ok(()) => { - // Check if we should reconnect - if *self.reconnect_on_close.read().await { - Err("Connection closed, will reconnect".to_string()) - } else { - Ok(()) - } - } - Err(e) => Err(format!("Read loop error: {}", e)), - } - } - - // ------------------------------------------------------------------------- - // Identification Handshake - // ------------------------------------------------------------------------- - - async fn send_identification(&self) { - let id = rand_u32(); - - let omikron_id = env::var("ID") - .unwrap_or("0".to_string()) - .parse::() - .unwrap_or(0); - - let identify_msg = CommunicationValue::new(CommunicationType::identification) - .with_id(id) - .add_data(DataTypes::omikron_id, DataValue::Number(omikron_id)); - - WAITING_TASKS.insert( - id, - WaitingTask { - task: Box::new(|selfc, cv| { - if cv.is_type(CommunicationType::error_not_found) { - log_err!( - 0, - PrintType::Omega, - "Identification failed: Omikron ID not found" - ); - return false; - } - if !cv.is_type(CommunicationType::challenge) { - return false; - } - - tokio::spawn(async move { - if let Err(e) = selfc.handle_challenge(cv).await { - log_err!(0, PrintType::Omega, "Challenge handling failed: {}", e); - } - }); - true - }), - inserted_at: Instant::now(), - }, - ); - - self.send_message(&identify_msg).await; - } - - async fn handle_challenge(&self, cv: CommunicationValue) -> Result<(), String> { - let challenge = cv - .get_data(DataTypes::challenge) - .as_str() - .ok_or("Challenge not found")?; - - let server_pub_key = cv - .get_data(DataTypes::public_key) - .as_str() - .ok_or("Public key not found")?; - - let decrypted_challenge = decrypt_b64( - &secret_key_to_base64(&get_private_key()), - server_pub_key, - challenge, - ) - .map_err(|e| format!("Decryption failed: {:?}", e))?; - - let response_msg = CommunicationValue::new(CommunicationType::challenge_response) - .with_id(cv.get_id()) - .add_data(DataTypes::challenge, DataValue::Str(decrypted_challenge)); - - let response_id = response_msg.get_id(); - - WAITING_TASKS.insert( - response_id, - WaitingTask { - task: Box::new(|selfc, final_cv| { - if !final_cv.is_type(CommunicationType::identification_response) { - log_err!(0, PrintType::Omega, "Expected identification_response"); - return false; - } - - let accepted = final_cv - .get_data(DataTypes::accepted) - .as_bool() - .unwrap_or(false); - - if !accepted { - log_err!(0, PrintType::Omega, "Omega did not accept identification"); - return false; - } - - tokio::spawn(async move { - let mut state = selfc.state.write().await; - if let ConnectionState::Connected { identified: _ } = *state { - *state = ConnectionState::Connected { identified: true }; - } - drop(state); - - selfc.sync_client_iota_status().await; - }); - - log!(0, PrintType::Omega, "Successfully identified with Omega"); - true - }), - inserted_at: Instant::now(), - }, - ); - - self.send_message(&response_msg).await; - Ok(()) - } - - async fn sync_client_iota_status(self: Arc) { - let mut connected_iota_ids: Vec = Vec::new(); - let mut connected_user_ids: Vec = Vec::new(); - - let rho_connections_reader = RHO_CONNECTIONS.read().await; - - for iota_id in rho_connections_reader.keys() { - connected_iota_ids.push(DataValue::Number(*iota_id)); - } - - for rho in rho_connections_reader.values() { - for client_conn in rho.get_client_connections().await { - connected_user_ids.push(DataValue::Number(client_conn.get_user_id().await as i64)); - } - } - - drop(rho_connections_reader); - - let sync_msg = CommunicationValue::new(CommunicationType::sync_client_iota_status) - .add_data(DataTypes::iota_ids, DataValue::Array(connected_iota_ids)) - .add_data(DataTypes::user_ids, DataValue::Array(connected_user_ids)) - .add_data( - DataTypes::rho_connections, - DataValue::Number(connection_count().await as i64), - ); - - self.send_message(&sync_msg).await; - } - - // ------------------------------------------------------------------------- - // Read Loop & Heartbeat - // ------------------------------------------------------------------------- - - async fn read_loop( - self: Arc, - receiver: &mut Receiver, - sender_handle: Arc, - ) { - // Monitor both receiver and sender handle for close - let mut close_rx = sender_handle.subscribe_close(); - - loop { - tokio::select! { - result = receiver.receive() => { - match result { - Ok(cv) => { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_in!(PrintType::Omega, &cv); - } - - if cv.is_type(CommunicationType::pong) || cv.is_type(CommunicationType::ping) { - self.handle_pong(&cv).await; - continue; - } - - let msg_id = cv.get_id(); - if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { - if (task.task)(self.clone(), cv.clone()) { - continue; - } - } - - if cv.is_type(CommunicationType::iota_user_data) { - if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { - let mut user_ids: Vec = Vec::new(); - for value in users { - if let DataValue::Number(user_id) = value { - user_ids.push(*user_id as u64); - } - } - let connections = crate::rho::rho_manager::RHO_CONNECTIONS.read().await; - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - if let Some(rho) = connections.get(&iota_id) { - rho.get_iota_connection().set_user_ids(user_ids).await; - } - } else { - for rho in connections.values() { - rho.get_iota_connection().set_user_ids(user_ids.clone()).await; - } - } - } - } - } - Err(e) => { - log_err!(0, PrintType::Omega, "Receive error: {}", e); - break; - } - } - } - _ = close_rx.changed() => { - // Connection was closed by either side - if let Some(reason) = close_rx.borrow().clone() { - log_err!(0, PrintType::Omega, "Connection closed: {:?}", reason); - } else { - log_in!(0, PrintType::Omega, "Connection closed cleanly"); - } - break; - } - } - } - } - - async fn heartbeat_loop(self: Arc) { - loop { - sleep(HEARTBEAT_INTERVAL).await; - - // Check if still connected - if !self.state.read().await.is_connected() { - break; - } - - // Check if sender is closed - if let Some(sender) = self.sender.read().await.as_ref() { - if sender.is_closed() { - log_err!(0, PrintType::Omega, "Sender closed, stopping heartbeat"); - break; - } - } else { - break; - } - - self.send_ping().await; - } - } - - async fn send_ping(&self) { - let ping = CommunicationValue::new(CommunicationType::ping).add_data( - DataTypes::send_time, - DataValue::Number( - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as i64, - ), - ); - self.send_message(&ping).await; - } - - async fn handle_pong(&self, cv: &CommunicationValue) { - let timestamp = cv - .get_data(DataTypes::send_time) - .as_number() - .unwrap_or_else(|| { - std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_secs() as i64 - }); - - *self.last_ping.lock().await = timestamp; - } - - // ------------------------------------------------------------------------- - // Public API - // ------------------------------------------------------------------------- - - pub async fn send_message(&self, cv: &CommunicationValue) { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_out!(PrintType::Omega, &cv); - } - - let sender_guard = self.sender.read().await; - if let Some(sender) = sender_guard.as_ref() { - // Check if closed before sending - if sender.is_closed() { - log_err!(0, PrintType::Omega, "Cannot send: connection closed"); - drop(sender_guard); - // Trigger reconnection by closing the connection state - if let Some(sender) = self.sender.write().await.take() { - sender.close(); - } - return; - } - - let sender_clone = Arc::clone(sender); - drop(sender_guard); - - if let Err(e) = sender_clone.send(cv).await { - log_err!(0, PrintType::Omega, "Send failed: {}", e); - } - } else { - log_err!(0, PrintType::Omega, "Cannot send: not connected"); - } - } - - pub async fn await_connection(&self, timeout_duration: Option) -> Result<(), String> { - if self.state.read().await.is_connected() { - return Ok(()); - } - - let timeout = timeout_duration.unwrap_or(CONNECTION_TIMEOUT); - let start = Instant::now(); - - loop { - if self.state.read().await.is_connected() { - return Ok(()); - } - - if start.elapsed() >= timeout { - return Err(format!( - "Connection not established within {} seconds", - timeout.as_secs() - )); - } - - sleep(Duration::from_millis(100)).await; - } - } - - pub async fn await_response( - &self, - cv: &CommunicationValue, - timeout_duration: Option, - ) -> Result { - self.await_connection(timeout_duration).await?; - - let (tx, mut rx) = mpsc::channel(1); - let msg_id = cv.get_id(); - - WAITING_TASKS.insert( - msg_id, - WaitingTask { - task: Box::new(move |_, response_cv| { - let inner_tx = tx.clone(); - tokio::spawn(async move { - let _ = inner_tx.send(response_cv).await; - }); - true - }), - inserted_at: Instant::now(), - }, - ); - - self.send_message(cv).await; - - let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); - - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response_cv)) => Ok(response_cv), - Ok(_) => Err("Channel closed".to_string()), - Err(_) => { - WAITING_TASKS.remove(&msg_id); - Err("Request timed out".to_string()) - } - } - } - - #[allow(dead_code)] - pub async fn is_connected(&self) -> bool { - self.state.read().await.is_connected() - } - #[allow(dead_code)] - pub async fn is_identified(&self) -> bool { - self.state.read().await.is_identified() - } - - #[allow(dead_code)] - pub async fn close_iota(iota_id: i64) { - let cv = CommunicationValue::new(CommunicationType::iota_disconnected) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); - OMEGA_CONNECTION.send_message(&cv).await; - } - - pub async fn client_changed(_iota_id: i64, user_id: i64, state: UserStatus) { - let msg_type = match state { - UserStatus::iota_offline => CommunicationType::user_disconnected, - UserStatus::user_offline => CommunicationType::user_disconnected, - _ => CommunicationType::user_connected, - }; - - let cv = CommunicationValue::new(msg_type) - .add_data(DataTypes::user_id, DataValue::Number(user_id)); - OMEGA_CONNECTION.send_message(&cv).await; - } - - pub async fn user_states(user_id: i64, user_ids: Vec) { - let user_ids = user_ids.iter().map(|v| DataValue::Number(*v)).collect(); - - let cv = CommunicationValue::new(CommunicationType::get_states) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); - let msg_id = cv.get_id(); - - WAITING_TASKS.insert( - msg_id, - WaitingTask { - task: Box::new( - move |_: Arc, response: CommunicationValue| { - tokio::spawn(async move { - 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 - }, - ), - inserted_at: Instant::now(), - }, - ); - - OMEGA_CONNECTION.send_message(&cv).await; - } -} - -// ============================================================================ -// Global Instance -// ============================================================================ - -static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { - let conn = Arc::new(OmegaConnection::new()); - - // Start the connection manager immediately - let conn_clone = conn.clone(); - tokio::spawn(async move { - conn_clone.start().await; - }); - - start_task_cleanup_loop(); - - conn -}); - -pub fn get_omega_connection() -> Arc { - OMEGA_CONNECTION.clone() -} diff --git a/src/omega/ping_pong_task.rs b/src/omega/ping_pong_task.rs deleted file mode 100644 index c7fc0b9..0000000 --- a/src/omega/ping_pong_task.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::time::Duration; -use tokio::time::Instant; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; - -use crate::omega::omega_connection::OmegaConnection; - -const PING_TIMEOUT: Duration = Duration::from_secs(30); - -impl OmegaConnection { - pub async fn send_ping(&self) { - let id = rand_u32(); - let send_time = Instant::now(); - - let mut message_send_times = self.message_send_times.lock().await; - message_send_times.retain(|_uuid, time| time.elapsed() < PING_TIMEOUT); - message_send_times.insert(id as i64, send_time); - - self.send_ping_message(id).await; - } - - pub async fn send_ping_message(&self, id: u32) { - let ping_message = CommunicationValue::new(CommunicationType::ping) - .with_id(id) - .add_data( - DataTypes::last_ping, - DataValue::Number(self.last_ping.lock().await.unwrap()), - ); - - self.send_message(&ping_message).await; - } - - /// Handles incoming pong and calculates latency - pub async fn handle_pong(&self, cv: &CommunicationValue, _log: bool) { - let id = cv.get_id(); - let mut message_send_times = self.message_send_times.lock().await; - if let Some(send_time) = message_send_times.remove(&(id as i64)) { - let ping = Instant::now().duration_since(send_time).as_millis() as i64; - *self.last_ping.lock().await = ping; - } - } -} diff --git a/src/rho/client_connection.rs b/src/rho/client_connection.rs deleted file mode 100644 index ebaabf2..0000000 --- a/src/rho/client_connection.rs +++ /dev/null @@ -1,583 +0,0 @@ -use crate::anonymous_clients::anonymous_manager; -use crate::calls::{call_manager, call_util}; -use crate::omega::omega_connection::get_omega_connection; -use crate::rho::connection::GeneralConnection; -use crate::rho::{rho_connection::RhoConnection, rho_manager}; -use crate::util::logger::PrintType; -use crate::{data::user::UserStatus, omega::omega_connection::OmegaConnection}; -use crate::{log_cv_in, log_cv_out, log_err, log_in, log_out}; -use std::str::FromStr; -use std::sync::Arc; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Receiver, Sender}; -use uuid::Uuid; - -pub struct ClientConnection { - pub user_id: u64, - - pub sender: Arc, - pub receiver: Arc, - pub ping: Arc>, - pub_key: Arc>>>, - pub rho_connection: Arc>>>, - pub interested_users: Arc>>, - is_open: Arc>, -} - -impl ClientConnection { - pub async fn from_general(general: Arc, user_id: u64) -> Arc { - Arc::new(Self { - ping: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - rho_connection: general.rho_connection.clone(), - interested_users: Arc::new(RwLock::new(Vec::new())), - is_open: Arc::new(RwLock::new(true)), - sender: general.sender.clone(), - receiver: general.receiver.clone(), - user_id: user_id, - }) - } - pub fn start(self: Arc) { - let self_clone = self.clone(); - tokio::spawn(async move { - while let Ok(cv) = self_clone.receiver.receive().await { - self_clone.clone().handle_message(cv).await; - } - self_clone.handle_close().await; - }); - - let self_clone2 = self.clone(); - tokio::spawn(async move { - tokio::time::sleep(Duration::from_millis(50)).await; - if self_clone2.get_rho_connection().await.is_none() { - self_clone2 - .send_error_response(0, CommunicationType::error) - .await; - } - }); - } - - /// Get the user ID - pub async fn get_user_id(&self) -> u64 { - self.user_id - } - - /// Get current ping - pub async fn get_ping(&self) -> i64 { - *self.ping.read().await - } - - /// Get RhoConnection if available - pub async fn get_rho_connection(&self) -> Option> { - self.rho_connection.read().await.clone() - } - - /// Send a CommunicationValue to the client - pub async fn send_message(self: Arc, cv: &CommunicationValue) { - if !*self.is_open.read().await { - log_out!( - self.user_id as i64, - PrintType::Client, - "Attempted to send message to a closed connection." - ); - return; - } - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { - log_cv_out!(PrintType::Client, &cv); - } - let _ = self.sender.send(&cv).await; - } - - /// Handle incoming message from client - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - tokio::spawn(async move { - if cv.is_type(CommunicationType::ping) { - self.handle_ping(cv).await; - return; - } - log_cv_in!(PrintType::Client, cv); - - // 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::call_token) { - self.handle_get_call(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_disconnect_user) { - self.handle_call_disconnect_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_timeout_user) { - self.handle_call_timeout_user(cv).await; - return; - } - - if cv.is_type(CommunicationType::call_set_anonymous_joining) { - self.handle_call_set_anonymous_joining(cv).await; - return; - } - if cv.is_type(CommunicationType::get_user_data) { - if let Some(anonymous) = { - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { - anonymous_manager::get_anonymous_user(user_id as u64).await - } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { - anonymous_manager::get_anonymous_user_by_name(username.to_string()).await - } else { - None - } - } { - let response = CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.get_id()) - .add_data( - DataTypes::username, - DataValue::Str(anonymous.get_user_name().await), - ) - .add_data( - DataTypes::user_id, - DataValue::Number(anonymous.get_user_id() as i64), - ) - .add_data( - DataTypes::display, - DataValue::Str(anonymous.get_display_name().await), - ) - .add_data( - DataTypes::avatar, - DataValue::Str(anonymous.get_avatar().await), - ) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); - - self.send_message(&response).await; - - return; - } - } - - if cv.is_type(CommunicationType::change_user_data) - || cv.is_type(CommunicationType::read_notification) - || cv.is_type(CommunicationType::get_notifications) - || cv.is_type(CommunicationType::get_user_data) - || cv.is_type(CommunicationType::get_iota_data) - || cv.is_type(CommunicationType::delete_user) - { - let sender = self.get_user_id().await; - self.handle_omega_forward(cv.with_sender(sender as u64)) - .await; - return; - } - // Forward other messages to Iota - self.forward_to_iota(cv).await; - }); - } - async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { - let client_for_closure = self.clone(); - tokio::spawn(async move { - let response_cv = get_omega_connection() - .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) - .await; - if let Ok(response_cv) = response_cv { - client_for_closure.send_message(&response_cv).await; - } - }); - } - - /// Handle ping message - async fn handle_ping(self: Arc, cv: CommunicationValue) { - // Update our ping if provided - if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - let current = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(); - let mut ping_guard = self.ping.write().await; - *ping_guard = current as i64 - last_ping; - } - - // 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(DataTypes::ping_iota, DataValue::Number(iota_ping)); - - self.send_message(&response).await; - } - - /// Handle client status change - async fn handle_client_changed(self: Arc, cv: CommunicationValue) { - let user_id = self.get_user_id().await; - if let DataValue::Str(_status_str) = cv.get_data(DataTypes::user_state) { - let user_status = UserStatus::user_online; - if let Some(rho_conn) = self.get_rho_connection().await { - OmegaConnection::client_changed( - rho_conn.get_iota_id().await as i64, - user_id as i64, - user_status, - ) - .await; - } - } - } - - /// Handle call invite - async fn handle_call_invite(self: Arc, cv: CommunicationValue) { - let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); - if receiver_id == 0 { - self.send_error_response(cv.get_id(), CommunicationType::error_no_user_id) - .await; - return; - } - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { - Ok(id) => id, - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) - .await; - return; - } - }, - _ => { - self.send_error_response(cv.get_id(), CommunicationType::error_no_call_id) - .await; - return; - } - }; - - let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; - if !invited { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) - .await; - return; - } - - // Find target RhoConnection - let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { - Some(rho) => rho, - _ => { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - // Get sender user ID - let sender_id = self.get_user_id().await; - - // Create and send call distribution message - let forward = CommunicationValue::new(CommunicationType::call_invite) - .with_receiver(receiver_id as u64) - .with_sender(sender_id as u64) - .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) - .add_data( - DataTypes::receiver_id, - DataValue::Str(receiver_id.to_string()), - ) - .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); - - target_rho.message_to_client(forward).await; - - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send_message(&response).await; - } - - /// Handle get call request - async fn handle_get_call(self: Arc, cv: CommunicationValue) { - let user_id = self.get_user_id().await; - - let call_id = match cv.get_data(DataTypes::call_id) { - DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { - Ok(id) => id, - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - }, - _ => { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - }; - - if let Some(token) = call_manager::get_call_token(user_id, call_id).await { - let response = CommunicationValue::new(CommunicationType::call_token) - .with_id(cv.get_id()) - .with_receiver(user_id as u64) - .add_data(DataTypes::call_token, DataValue::Str(token)); - self.send_message(&response).await; - } else { - self.send_error_response(cv.get_id(), CommunicationType::error) - .await; - return; - } - } - async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id().await) - .await - .unwrap() - .has_admin() - { - let _ = call_util::remove_participant(call_id, user_id as u64).await; - call.get_caller(user_id as u64) - .await - .unwrap() - .set_timeout(untill) - .await; - } - } - } - async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - - let call = call_manager::get_call(call_id).await; - if let Some(call) = call { - if call - .get_caller(self.get_user_id().await) - .await - .unwrap() - .has_admin() - { - call.remove_caller(user_id as u64).await; - } - } - } - async fn handle_call_set_anonymous_joining(self: Arc, cv: CommunicationValue) { - let call_id = - Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); - let enable = cv.get_data(DataTypes::enabled).as_bool().unwrap_or(true); - - let call = call_manager::get_call(call_id).await; - - let mut short_link = None; - if let Some(call) = call { - if call - .get_caller(self.get_user_id().await) - .await - .unwrap() - .has_admin() - { - call.set_anonymous_joining(enable).await; - } - short_link = call.get_short_link().await; - } - let mut response_cv = - CommunicationValue::new(CommunicationType::call_set_anonymous_joining) - .with_id(cv.get_id()) - .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) - .add_data(DataTypes::enabled, DataValue::Bool(enable)); - if let Some(short_link) = short_link { - response_cv = response_cv.add_data(DataTypes::link, DataValue::Str(short_link)); - } - self.send_message(&response_cv).await; - } - - /// Forward message to Iota - async fn forward_to_iota(self: Arc, cv: CommunicationValue) { - let sender_user_id = self.get_user_id().await; - let msg_id = cv.get_id(); - let msg_type = cv.get_type(); - - log_in!( - sender_user_id as i64, - PrintType::Client, - "Forwarding client->iota: sender={} type={:?} id={} receiver={}", - sender_user_id, - msg_type, - msg_id, - cv.get_receiver() - ); - - if cv.is_type(CommunicationType::add_conversation) - && cv - .get_data(DataTypes::chat_partner_id) - .as_number() - .is_none() - { - let chat_partner_name = cv - .get_data(DataTypes::chat_partner_name) - .as_str() - .unwrap_or("") - .to_string(); - - if anonymous_manager::get_anonymous_user_by_name(chat_partner_name.to_string()) - .await - .is_some() - { - self.send_error_response(cv.get_id(), CommunicationType::error_anonymous) - .await; - return; - } - - let load_uuid_response = get_omega_connection() - .await_response( - &CommunicationValue::new(CommunicationType::get_user_data) - .with_id(cv.clone().get_id()) - .add_data( - DataTypes::username, - DataValue::Str(chat_partner_name.clone()), - ), - Some(Duration::from_secs(20)), - ) - .await; - let chat_partner_id = { - if let Ok(load_uuid_response) = load_uuid_response { - load_uuid_response.get_data(DataTypes::user_id).clone() - } else { - DataValue::Null - } - }; - - if let Some(rho_conn) = self.get_rho_connection().await { - let iota_id = rho_conn.get_iota_id().await; - log_in!( - sender_user_id as i64, - PrintType::Client, - "Resolved rho for add_conversation: sender={} -> iota_id={} id={}", - sender_user_id, - iota_id, - msg_id - ); - - let updated_cv = cv - .with_sender(sender_user_id as u64) - .add_data(DataTypes::chat_partner_id, chat_partner_id); - rho_conn.message_to_iota(updated_cv).await; - } else { - log_err!( - sender_user_id as i64, - PrintType::Client, - "No rho/iota mapping found for add_conversation sender={} type={:?} id={}", - sender_user_id, - msg_type, - msg_id - ); - } - return; - } - - if let Some(rho_conn) = self.get_rho_connection().await { - let iota_id = rho_conn.get_iota_id().await; - log_in!( - sender_user_id as i64, - PrintType::Client, - "Resolved rho for forward: sender={} -> iota_id={} type={:?} id={}", - sender_user_id, - iota_id, - msg_type, - msg_id - ); - - let updated_cv = cv.with_sender(sender_user_id as u64); - rho_conn.message_to_iota(updated_cv).await; - } else { - log_err!( - sender_user_id as i64, - PrintType::Client, - "No rho/iota mapping found for sender={} type={:?} id={}", - sender_user_id, - msg_type, - msg_id - ); - self.send_error_response(msg_id, CommunicationType::error) - .await; - } - } - - /// Send error response - async fn send_error_response(self: Arc, message_id: u32, 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 is_open_guard = self.is_open.write().await; - if !*is_open_guard { - return; - } - *is_open_guard = false; - - let _ = self.sender.close(); - } - - /// Set interested users list - pub async fn set_interested_users(self: Arc, interested_ids: Vec) { - let mut interested_guard = self.interested_users.write().await; - *interested_guard = interested_ids; - } - #[allow(dead_code)] - pub async fn get_interested_users(self: Arc) -> Vec { - let interested_guard = self.interested_users.read().await; - interested_guard.clone() - } - - /// Check if interested in a user and send notification - #[allow(dead_code)] - pub async fn are_you_interested(self: Arc, user_id: i64) { - let interested_guard = self.clone().get_interested_users().await; - if interested_guard.contains(&user_id) { - let notification = CommunicationValue::new(CommunicationType::client_changed) - .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) - .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); - - self.send_message(¬ification).await; - } - } - - /// Handle connection close - pub async fn handle_close(&self) { - let user_id = self.get_user_id().await; - if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id as i64).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 { - sender: Arc::clone(&self.sender), - receiver: Arc::clone(&self.receiver), - user_id: self.user_id, - ping: Arc::clone(&self.ping), - pub_key: Arc::clone(&self.pub_key), - rho_connection: Arc::clone(&self.rho_connection), - interested_users: Arc::clone(&self.interested_users), - is_open: Arc::clone(&self.is_open), - } - } -} diff --git a/src/rho/connection.rs b/src/rho/connection.rs deleted file mode 100755 index ca746f0..0000000 --- a/src/rho/connection.rs +++ /dev/null @@ -1,358 +0,0 @@ -use rand::{Rng, distributions::Alphanumeric}; -use std::{sync::Arc, time::Duration}; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Receiver, Sender}; - -use crate::{ - anonymous_clients::anonymous_client_connection::AnonymousClientConnection, - get_private_key, get_public_key, log_cv_in, log_cv_out, log_err, log_in, log_out, - omega::omega_connection::get_omega_connection, - rho::{ - client_connection::ClientConnection, iota_connection::IotaConnection, - rho_connection::RhoConnection, rho_manager, - }, - util::{ - crypto_helper::{load_public_key, public_key_to_base64}, - crypto_util::{DataFormat, SecurePayload}, - logger::PrintType, - }, -}; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[allow(dead_code)] -pub enum ConnectionKind { - Client, - Iota, - AnonymousClient, - Phi, -} - -pub struct GeneralConnection { - pub sender: Arc, - pub receiver: Arc, - - identified: Arc>, - challenged: Arc>, - challenge: Arc>, - - connection_kind: Arc>>, - pub rho_connection: Arc>>>, - id: Arc>, - - pub_key: Arc>>>, -} -impl GeneralConnection { - pub fn new(sender: Sender, receiver: Receiver) -> Arc { - Arc::new(Self { - sender: Arc::new(sender), - receiver: Arc::new(receiver), - identified: Arc::new(RwLock::new(false)), - challenged: Arc::new(RwLock::new(false)), - challenge: Arc::new(RwLock::new(String::new())), - connection_kind: Arc::new(RwLock::new(None)), - rho_connection: Arc::new(RwLock::new(None)), - id: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - }) - } -} -impl GeneralConnection { - pub async fn handle(self: Arc) { - log_in!(0, PrintType::General, "General connection handler started"); - - loop { - let cv = match self.receiver.receive().await { - Ok(v) => v, - Err(_) => { - break; - } - }; - - log_cv_in!(cv); - - if !*self.identified.read().await { - self.handle_identification(cv).await; - continue; - } - - if !*self.challenged.read().await { - self.handle_challenge_response(cv).await; - } - - if *self.challenged.read().await { - let self_clone = self.clone(); - tokio::spawn(async move { - self_clone.migrate().await; - }); - break; - } - } - - log_out!(0, PrintType::General, "General connection handler stopped"); - } - async fn handle_identification(self: &Arc, cv: CommunicationValue) { - if !cv.is_type(CommunicationType::identification) { - return; - } - - if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { - *self.id.write().await = *iota_id as u64; - *self.connection_kind.write().await = Some(ConnectionKind::Iota); - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) - .add_data(DataTypes::iota_id, DataValue::Number(*iota_id)); - - let response_cv = get_omega_connection() - .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) - .await; - - let response_cv = match response_cv { - Ok(r) => r, - Err(_) => { - return; - } - }; - - let base64_pub = response_cv - .get_data(DataTypes::public_key) - .as_str() - .unwrap_or(""); - - let pub_key = match load_public_key(base64_pub) { - Some(pk) => pk, - None => { - return; - } - }; - - *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge.clone(); - *self.identified.write().await = true; - - let encrypted_challenge = - SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(public_key_to_base64(&get_public_key())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); - - log_cv_out!(response); - let _ = self.sender.send(&response).await; - } else if let DataValue::Number(user_id) = cv.get_data(DataTypes::user_id) { - *self.id.write().await = *user_id as u64; - *self.connection_kind.write().await = Some(ConnectionKind::Client); - - let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) - .add_data(DataTypes::user_id, DataValue::Number(*user_id)); - - let response_cv = get_omega_connection() - .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) - .await; - - let response_cv = match response_cv { - Ok(r) => r, - Err(_) => { - return; - } - }; - - let base64_pub = response_cv - .get_data(DataTypes::public_key) - .as_str() - .unwrap_or(""); - - let pub_key = match load_public_key(base64_pub) { - Some(pk) => pk, - None => { - return; - } - }; - - *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); - - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - *self.challenge.write().await = challenge.clone(); - *self.identified.write().await = true; - - let encrypted_challenge = - SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) - .unwrap() - .encrypt_x448(pub_key) - .unwrap() - .export(DataFormat::Base64); - - let response = CommunicationValue::new(CommunicationType::challenge) - .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, - DataValue::Str(public_key_to_base64(&get_public_key())), - ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); - - log_cv_out!(response); - let _ = self.sender.send(&response).await; - } - } - async fn handle_challenge_response(self: &Arc, cv: CommunicationValue) { - let id = *self.id.read().await as i64; - - if !cv.is_type(CommunicationType::challenge_response) { - return; - } - - if let DataValue::Str(response) = cv.get_data(DataTypes::challenge) { - let expected = self.challenge.read().await.clone(); - - if *response == expected { - *self.challenged.write().await = true; - - let response = CommunicationValue::new(CommunicationType::identification_response) - .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::Bool(true)); - - log_cv_out!(response); - if let Err(_) = self.sender.send(&response).await { - return; - } - } else { - log_err!( - id, - PrintType::Iota, - "Challenge response mismatch expected={} actual={}", - expected, - response - ); - } - } else { - log_err!( - id, - PrintType::Iota, - "Challenge response missing challenge payload" - ); - } - } - - async fn migrate(self: &Arc) -> bool { - let kind = match *self.connection_kind.read().await { - Some(kind) => kind, - None => { - return false; - } - }; - let id = *self.id.read().await; - - match kind { - ConnectionKind::Client => { - let notify = CommunicationValue::new(CommunicationType::user_connected) - .add_data(DataTypes::user_id, DataValue::Number(id as i64)); - get_omega_connection().send_message(¬ify).await; - - let user_id = id as i64; - - let client = ClientConnection::from_general(self.clone(), id).await; - - let mut rho = rho_manager::get_rho_con_for_user(user_id).await; - - if rho.is_none() { - let get_user_msg = CommunicationValue::new(CommunicationType::get_user_data) - .add_data(DataTypes::user_id, DataValue::Number(user_id)); - - if let Ok(user_data_cv) = get_omega_connection() - .await_response(&get_user_msg, Some(Duration::from_secs(20))) - .await - { - if let DataValue::Number(iota_id) = - user_data_cv.get_data(DataTypes::iota_id) - { - if let Some(bound_rho) = - rho_manager::bind_user_to_iota(user_id, *iota_id).await - { - bound_rho.bind_user_id(user_id).await; - rho = Some(bound_rho); - } - } - } - } - - *self.rho_connection.write().await = rho.clone(); - - if let Some(rho_conn) = rho { - rho_conn.bind_user_id(user_id).await; - rho_conn.add_client_connection(client.clone()).await; - } else { - log_err!( - user_id, - PrintType::Client, - "No RhoConnection found for user {}, client not attached to iota", - id - ); - } - - client.start(); - } - ConnectionKind::Iota => { - let notify = CommunicationValue::new(CommunicationType::iota_connected) - .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); - get_omega_connection().send_message(¬ify).await; - - let iota = IotaConnection::from_general(self.clone(), id).await; - - let rho = Arc::new(RhoConnection::new(iota.clone(), Vec::new()).await); - - iota.set_rho_connection(rho.clone()).await; - - rho_manager::add_rho(rho).await; - - let get_iota_msg = CommunicationValue::new(CommunicationType::get_iota_data) - .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); - - if let Ok(iota_data_cv) = get_omega_connection() - .await_response(&get_iota_msg, Some(Duration::from_secs(20))) - .await - { - if let DataValue::Array(users) = iota_data_cv.get_data(DataTypes::user_ids) { - let mut user_ids: Vec = Vec::new(); - for value in users { - if let DataValue::Number(user_id) = value { - user_ids.push(*user_id as u64); - } - } - iota.set_user_ids(user_ids).await; - } - } - - iota.clone().start(); - } - ConnectionKind::AnonymousClient => { - let client = AnonymousClientConnection::from_general(self.clone(), id).await; - client.start(); - } - ConnectionKind::Phi => { - let phi = ClientConnection::from_general(self.clone(), id).await; - phi.start(); - } - } - true - } -} diff --git a/src/rho/iota_connection.rs b/src/rho/iota_connection.rs deleted file mode 100755 index 41336ea..0000000 --- a/src/rho/iota_connection.rs +++ /dev/null @@ -1,461 +0,0 @@ -use crate::calls::call_group::CallGroup; -use crate::calls::call_manager; -use crate::log_cv_in; -use crate::log_cv_out; -use crate::log_err; -use crate::log_in; -use crate::omega::omega_connection::get_omega_connection; -use crate::rho::connection::GeneralConnection; -use crate::util::logger::PrintType; -use dashmap::DashMap; -use std::collections::BTreeMap; -use std::{collections::HashMap, sync::Arc, time::Duration}; -use tokio::sync::RwLock; -use tokio::sync::mpsc; -use ttp_core::CommunicationType; -use ttp_core::CommunicationValue; -use ttp_core::DataTypes; -use ttp_core::DataValue; -use ttp_native::Receiver; -use ttp_native::Sender; -use x448::PublicKey; - -use super::{rho_connection::RhoConnection, rho_manager}; -use crate::omega::omega_connection::OmegaConnection; - -#[allow(dead_code)] -pub struct IotaConnection { - pub iota_id: u64, - pub sender: Arc, - pub receiver: Arc, - pub user_ids: Arc>>, - pub ping: Arc>, - pub_key: Arc>>>, - pub waiting_tasks: - DashMap, CommunicationValue) -> bool + Send + Sync>>, - pub rho_connection: Arc>>>, -} - -impl IotaConnection { - pub async fn from_general(general: Arc, iota_id: u64) -> Arc { - Arc::new(Self { - ping: Arc::new(RwLock::new(0)), - pub_key: Arc::new(RwLock::new(None)), - rho_connection: general.rho_connection.clone(), - user_ids: Arc::new(RwLock::new(Vec::new())), - sender: general.sender.clone(), - receiver: general.receiver.clone(), - iota_id: iota_id, - waiting_tasks: DashMap::new(), - }) - } - pub fn start(self: Arc) { - let self_clone = self.clone(); - tokio::spawn(async move { - loop { - match self_clone.receiver.receive().await { - Ok(cv) => { - self_clone.clone().handle_message(cv).await; - } - Err(_) => { - break; - } - } - } - self_clone.handle_close().await; - }); - } - - /// Get the Iota ID - pub async fn get_iota_id(&self) -> u64 { - self.iota_id - } - - #[allow(dead_code)] - pub async fn get_public_key(&self) -> Option { - if let Some(public_key) = self.pub_key.read().await.clone() { - PublicKey::from_bytes(&public_key) - } else { - None - } - } - - /// Get the user IDs - pub async fn get_user_ids(&self) -> Vec { - self.user_ids.read().await.clone() - } - - /// Replace all users linked to this iota and synchronize the attached rho mapping. - pub async fn set_user_ids(&self, user_ids: Vec) { - { - let mut guard = self.user_ids.write().await; - *guard = user_ids.clone(); - } - - if let Some(rho_conn) = self.get_rho_connection().await { - let user_ids_i64: Vec = user_ids.into_iter().map(|u| u as i64).collect(); - rho_conn.set_user_ids(user_ids_i64).await; - } - } - - pub async fn add_user_id(&self, user_id: u64) { - let mut should_sync = false; - { - let mut guard = self.user_ids.write().await; - if !guard.contains(&user_id) { - guard.push(user_id); - should_sync = true; - } - } - - if should_sync { - if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn.add_user_id(user_id as i64).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: Arc) { - 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() { - Some(weak_ref.clone()) - } else { - None - } - } - - /// Send a CommunicationValue to the Iota - pub async fn send_message(&self, cv: &CommunicationValue) { - if !cv.is_type(CommunicationType::pong) { - log_cv_out!(PrintType::Iota, cv); - } - if let Err(e) = self.sender.send(&cv).await { - log_err!( - self.iota_id as i64, - PrintType::Iota, - "Failed to send message: {:?}", - e - ); - } - } - - /// Handle incoming message from Iota - pub async fn handle_message(self: Arc, cv: CommunicationValue) { - let msg_id = cv.get_id(); - if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { - if (task)(self.clone(), cv.clone()) { - return; - } - } - - // Handle ping - if cv.is_type(CommunicationType::ping) || cv.is_type(CommunicationType::pong) { - self.handle_ping(cv).await; - return; - } - - log_cv_in!(PrintType::Iota, cv); - - // Handle GET_CHATS - if cv.is_type(CommunicationType::get_chats) { - self.handle_get_chats(cv).await; - return; - } - - // Handle forwarding to other Iotas or clients - let receiver_id = cv.get_receiver(); - if (receiver_id != 0 && !self.get_user_ids().await.contains(&(receiver_id as u64))) - || cv.is_type(CommunicationType::message_other_iota) - || cv.is_type(CommunicationType::send_chat) - { - self.handle_forward_message(cv).await; - return; - } - - if cv.is_type(CommunicationType::change_iota_data) - || cv.is_type(CommunicationType::push_notification) - || cv.is_type(CommunicationType::get_user_data) - || cv.is_type(CommunicationType::get_iota_data) - || cv.is_type(CommunicationType::get_register) - || cv.is_type(CommunicationType::complete_register_user) - || cv.is_type(CommunicationType::delete_iota) - { - let sender = self.get_iota_id().await; - self.handle_omega_forward(cv.with_sender(sender as u64)) - .await; - return; - } - self.forward_to_client(cv).await; - } - - #[allow(dead_code)] - async fn send_error_response(&self, message_id: u32, error_type: CommunicationType) { - let error = CommunicationValue::new(error_type).with_id(message_id); - self.send_message(&error).await; - } - - #[allow(dead_code)] - async fn close(&self) { - let _ = self.sender.close(); - } - - async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { - let iota_for_closure = self.clone(); - tokio::spawn(async move { - let response_cv = get_omega_connection() - .await_response(&cv.with_sender(self.iota_id), Some(Duration::from_secs(20))) - .await; - if let Ok(response_cv) = response_cv { - iota_for_closure.send_message(&response_cv).await; - } - }); - } - /// Handle ping message - async fn handle_ping(&self, cv: CommunicationValue) { - if let DataValue::Number(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; - } - } - - let client_pings = if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn.get_client_pings().await - } else { - HashMap::new() - }; - - let pings: Vec<(DataTypes, DataValue)> = client_pings - .into_iter() - .map(|(k, v)| (DataTypes::parse(k), DataValue::Number(v))) - .collect(); - let response = CommunicationValue::new(CommunicationType::pong) - .with_id(cv.get_id()) - .add_data(DataTypes::ping_clients, DataValue::Container(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(); - let sender_id = cv.get_sender(); - let my_user_ids = self.get_user_ids().await; - - log_in!( - self.iota_id as i64, - PrintType::Iota, - "Authority check: sender_id={} receiver_id={} iota_user_ids={:?} msg_type={:?} msg_id={}", - sender_id, - receiver_id, - my_user_ids, - cv.get_type(), - cv.get_id() - ); - - if my_user_ids.contains(&(sender_id as u64)) { - if let Some(target_rho) = rho_manager::get_rho_con_for_user(receiver_id as i64).await { - target_rho.message_to_iota(cv).await; - } else { - let error = CommunicationValue::new(CommunicationType::error_no_iota) - .with_id(cv.get_id()) - .with_sender(cv.get_sender()); - self.send_message(&error).await; - } - } else { - log_err!( - self.iota_id as i64, - PrintType::Iota, - "Rejected client->iota forward: sender_id={} is not authorized for this iota. Known users={:?}", - sender_id, - my_user_ids - ); - - self.send_message( - &CommunicationValue::new(CommunicationType::error_invalid_user_id).add_data( - DataTypes::error_type, - DataValue::Str( - "You are sending to another User without authority.".to_string(), - ), - ), - ) - .await; - } - } - - /// Handle GET_CHATS message - async fn handle_get_chats(&self, cv: CommunicationValue) { - let receiver_id = cv.get_receiver(); - let mut interested_ids: Vec = Vec::new(); - - // ============================ - // Load Calls - // ============================ - let calls: Vec> = call_manager::get_call_groups(receiver_id).await; - - let mut invites: HashMap> = HashMap::new(); - let empty = calls.is_empty(); - - for call in calls { - for inviter in call.members.read().await.iter() { - let call_self = call.get_caller(receiver_id).await.unwrap(); - - let inviter_id = inviter.user_id; - let timeout = *call_self.timeout.read().await; - let admin = call_self.has_admin(); - - // Build call container - let mut call_map: BTreeMap = BTreeMap::new(); - - call_map.insert(DataTypes::call_id, DataValue::Str(call.call_id.to_string())); - - if timeout > 0 { - call_map.insert(DataTypes::timeout, DataValue::Number(timeout as i64)); - } - - if admin { - call_map.insert(DataTypes::has_admin, DataValue::Bool(true)); - } - - let call_container = DataValue::container_from_map(&call_map); - - invites - .entry(inviter_id as i64) - .or_insert_with(Vec::new) - .push(call_container); - } - } - - // ============================ - // Enrich Contacts - // ============================ - let enriched_contacts = if empty { - match cv.get_data(DataTypes::user_ids) { - DataValue::Array(arr) => DataValue::Array(arr.clone()), - _ => DataValue::Array(vec![]), - } - } else { - let mut enriched: Vec = Vec::new(); - - if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { - for user_val in users { - if let DataValue::Container(entries) = user_val { - let mut user_map: BTreeMap = - entries.iter().cloned().collect(); - - // extract user_id - if let Some(DataValue::Number(user_id)) = user_map.get(&DataTypes::user_id) - { - interested_ids.push(*user_id); - - // attach calls if exists - if let Some(call_list) = invites.get(user_id) { - user_map - .insert(DataTypes::calls, DataValue::Array(call_list.clone())); - } - } - - enriched.push(DataValue::container_from_map(&user_map)); - } - } - } - - DataValue::Array(enriched) - }; - - // ============================ - // Notify Omega - // ============================ - OmegaConnection::user_states(receiver_id as i64, interested_ids.clone()).await; - - // ============================ - // Notify Rho - // ============================ - if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn - .set_interested(receiver_id as i64, interested_ids) - .await; - } - - // ============================ - // Forward to client - // ============================ - self.forward_to_client(cv.add_data(DataTypes::user_ids, enriched_contacts)) - .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); - rho_conn.message_to_client(updated_cv).await; - } else { - } - } - - pub async fn handle_close(&self) { - if let Some(rho_conn) = self.get_rho_connection().await { - rho_conn.close_iota_connection().await; - } - } - - #[allow(dead_code)] - pub async fn await_response( - self: Arc, - cv: &CommunicationValue, - timeout_duration: Option, - ) -> Result { - let (tx, mut rx) = mpsc::channel(1); - let msg_id = cv.get_id(); - - let task_tx = tx.clone(); - self.waiting_tasks.insert( - msg_id, - Box::new(move |_, response_cv| { - let inner_tx = task_tx.clone(); - tokio::spawn(async move { - let _ = inner_tx.send(response_cv).await; - }); - true - }), - ); - - self.send_message(cv).await; - - let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); - - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response_cv)) => Ok(response_cv), - Ok(_) => Err("Failed to receive response, channel was closed.".to_string()), - Err(_) => { - self.waiting_tasks.remove(&msg_id); - Err(format!( - "Request timed out after {} seconds.", - timeout.as_secs() - )) - } - } - } -} - -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 deleted file mode 100644 index ac498f3..0000000 --- a/src/rho/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod client_connection; -pub mod connection; -pub mod iota_connection; -pub mod rho_connection; -pub mod rho_manager; -pub mod server; diff --git a/src/rho/rho_connection.rs b/src/rho/rho_connection.rs deleted file mode 100644 index 6a740ff..0000000 --- a/src/rho/rho_connection.rs +++ /dev/null @@ -1,199 +0,0 @@ -use super::{client_connection::ClientConnection, iota_connection::IotaConnection, rho_manager}; - -use crate::data::user::UserStatus; -use crate::omega::omega_connection::OmegaConnection; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::RwLock; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; - -pub struct RhoConnection { - iota_connection: Arc, - user_ids: Arc>>, - 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: Arc::new(RwLock::new(user_ids.clone())), - client_connections: Arc::new(RwLock::new(Vec::new())), - }; - - rho_connection - } - - pub async fn get_iota_id(&self) -> u64 { - self.iota_connection.iota_id - } - - pub async fn get_user_ids(&self) -> Vec { - self.user_ids.read().await.clone() - } - - pub async fn set_user_ids(&self, user_ids: Vec) { - let mut guard = self.user_ids.write().await; - *guard = user_ids; - } - - pub async fn add_user_id(&self, user_id: i64) { - let mut guard = self.user_ids.write().await; - if !guard.contains(&user_id) { - guard.push(user_id); - } - } - - pub async fn bind_user_id(&self, user_id: i64) { - self.add_user_id(user_id).await; - self.iota_connection.add_user_id(user_id as u64).await; - } - - pub fn get_iota_connection(&self) -> &Arc { - &self.iota_connection - } - - 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: i64, - ) -> Vec> { - let connections = self.client_connections.read().await; - let mut collections = Vec::new(); - for con in connections.iter() { - if con.get_user_id().await == user_id as u64 { - collections.push(con.clone()); - } - } - collections - } - - /// Add a client connection - #[allow(dead_code)] - pub async fn add_client_connection(&self, connection: Arc) { - let notification = CommunicationValue::new(CommunicationType::client_connected).add_data( - DataTypes::user_id, - DataValue::Number(connection.get_user_id().await as i64), - ); - - self.iota_connection.send_message(¬ification).await; - - { - let mut connections = self.client_connections.write().await; - connections.push(Arc::clone(&connection)); - } - - OmegaConnection::client_changed( - self.get_iota_id().await as i64, - connection.get_user_id().await as i64, - UserStatus::user_online, - ) - .await; - } - - /// Remove a client connection - pub async fn close_client_connection(&self, connection: Arc) { - let target_user_id = connection.get_user_id().await; - { - let mut connections = self.client_connections.write().await; - connections.retain(|con| { - futures::executor::block_on(async { con.get_user_id().await != target_user_id }) - }); - } - - // Notify OmegaConnection - OmegaConnection::client_changed( - self.get_iota_id().await as i64, - connection.get_user_id().await as i64, - UserStatus::user_offline, - ) - .await; - } - - /// 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 as i64).await; - - // Notify OmegaConnection - OmegaConnection::close_iota(self.get_iota_id().await as i64).await; - } - - /// Send message from Iota to specific client - pub async fn message_to_client(&self, cv: CommunicationValue) { - let connections = self.client_connections.read().await; - let receiver_id = cv.get_receiver(); - for connection in connections.iter() { - if connection.get_user_id().await == receiver_id { - connection.clone().send_message(&cv).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: i64, interested_ids: Vec) { - let connections = self.client_connections.read().await; - for connection in connections.iter() { - let conn_user_id = connection.get_user_id().await; - if conn_user_id == user_id as u64 { - connection - .clone() - .set_interested_users(interested_ids.clone()) - .await; - break; - } - } - } - - /// Check if clients are interested in a user - #[allow(dead_code)] - pub async fn are_they_interested(&self, user_id: i64) { - let connections = self.client_connections.read().await; - for connection in connections.iter() { - connection.clone().are_you_interested(user_id).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() { - let 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 - #[allow(dead_code)] - pub async fn contains_user(&self, user_id: &i64) -> bool { - self.user_ids.read().await.contains(user_id) - } - - /// Get count of active client connections - #[allow(dead_code)] - pub async fn client_count(&self) -> usize { - let connections = self.client_connections.read().await; - connections.len() - } -} diff --git a/src/rho/rho_manager.rs b/src/rho/rho_manager.rs deleted file mode 100644 index e823a04..0000000 --- a/src/rho/rho_manager.rs +++ /dev/null @@ -1,83 +0,0 @@ -use super::rho_connection::RhoConnection; -use crate::log_in; -use crate::util::logger::PrintType; -use std::{ - collections::HashMap, - sync::{Arc, LazyLock}, -}; -use tokio::sync::RwLock; - -pub static RHO_CONNECTIONS: LazyLock>>>> = - LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); - -pub async fn get_rho_con_for_user(user_id: i64) -> Option> { - let connections = RHO_CONNECTIONS.read().await; - for rho_connection in connections.values() { - let rho_user_ids = rho_connection.get_user_ids().await; - log_in!( - user_id, - PrintType::Client, - "Comparing user IDs: {:?}", - rho_user_ids - ); - if rho_user_ids.contains(&user_id) { - return Some(Arc::clone(rho_connection)); - } - } - None -} - -#[allow(dead_code)] -pub async fn contains_iota(iota_id: i64) -> bool { - let connections = RHO_CONNECTIONS.read().await; - connections.contains_key(&iota_id) -} - -/// Bind a user ID to an already tracked iota/rho connection. -pub async fn bind_user_to_iota(user_id: i64, iota_id: i64) -> Option> { - let connections = RHO_CONNECTIONS.read().await; - if let Some(rho_connection) = connections.get(&iota_id) { - let rho = Arc::clone(rho_connection); - drop(connections); - - rho.add_user_id(user_id).await; - - log_in!( - user_id, - PrintType::Client, - "Bound user {} to iota {}", - user_id, - iota_id - ); - - Some(rho) - } else { - None - } -} - -/// Remove a RhoConnection by Iota ID -pub async fn remove_rho(iota_id: i64) -> 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 as i64, rho_connection); -} - -/// Get a RhoConnection by Iota ID directly -#[allow(dead_code)] -pub async fn get_rho_by_iota(iota_id: i64) -> Option> { - let connections = RHO_CONNECTIONS.read().await; - connections.get(&iota_id).map(Arc::clone) -} - -/// Get the count of active connections -pub async fn connection_count() -> usize { - let connections = RHO_CONNECTIONS.read().await; - connections.len() -} diff --git a/src/rho/server.rs b/src/rho/server.rs deleted file mode 100644 index 9e3946d..0000000 --- a/src/rho/server.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::{ - log, - rho::connection::GeneralConnection, - util::{file_util::load_file_vec, logger::PrintType}, -}; -use ttp_native::Host; - -pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); - - let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); - - let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; - log!(0, PrintType::General, "Server listening on port {}", port); - - while let Some((sender, receiver)) = host.next().await { - tokio::spawn(async move { - let conn = GeneralConnection::new(sender, receiver); - conn.handle().await; - }); - } - log!(0, PrintType::General, "Server stopped"); - - Ok(()) -} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs deleted file mode 100755 index cb3ae6a..0000000 --- a/src/util/crypto_helper.rs +++ /dev/null @@ -1,149 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, OsRng}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use rand_core::RngCore; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret, SharedSecret}; - -/// Errors for crypto operations -#[allow(dead_code)] -#[derive(Debug)] -pub enum CryptoError { - Base64Decode(base64::DecodeError), - InvalidKey, - AgreementError, - EncryptionError(aes_gcm::Error), - DecryptionError(aes_gcm::Error), -} - -impl From for CryptoError { - fn from(err: base64::DecodeError) -> Self { - CryptoError::Base64Decode(err) - } -} - -#[allow(dead_code)] -pub struct KeyPair { - pub secret: Secret, - pub public: PublicKey, -} - -#[allow(dead_code)] -pub fn generate_keypair() -> KeyPair { - let mut buf = [0u8; 56]; - let mut rng = OsRng; - rng.fill_bytes(&mut buf); - let secret = Secret::from_bytes(&buf).unwrap(); - let public = PublicKey::from(&secret); - KeyPair { secret, public } -} - -pub fn public_key_to_base64(pubkey: &PublicKey) -> String { - STANDARD.encode(pubkey.as_bytes().as_ref()) -} - -pub fn secret_key_to_base64(secret: &Secret) -> String { - STANDARD.encode(secret.as_bytes().as_ref()) -} - -pub fn load_public_key(base64_pub: &str) -> Option { - let bytes = STANDARD.decode(base64_pub).unwrap(); - PublicKey::from_bytes(&bytes) -} - -pub fn load_secret_key(base64_secret: &str) -> Option { - let bytes = STANDARD.decode(base64_secret).unwrap(); - Secret::from_bytes(&bytes) -} - -fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(shared.as_bytes()); - let result = hasher.finalize(); - let mut key = [0u8; 32]; - key.copy_from_slice(&result[..32]); - key -} - -#[allow(dead_code)] -pub fn encrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - plaintext: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - encrypt(secret, peer_pub, plaintext) -} -pub fn encrypt( - secret: Secret, - peer_pub: PublicKey, - plaintext: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - let mut nonce_bytes = [0u8; 12]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - let ciphertext = cipher - .encrypt(nonce, plaintext.as_bytes()) - .map_err(CryptoError::EncryptionError)?; - // prefix nonce to ciphertext - let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); - out.extend_from_slice(&nonce_bytes); - out.extend_from_slice(&ciphertext); - Ok(STANDARD.encode(&out)) -} - -pub fn decrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - encrypted_base64: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - decrypt(secret, peer_pub, encrypted_base64) -} -pub fn decrypt( - secret: Secret, - peer_pub: PublicKey, - encrypted_base64: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - - let encrypted = STANDARD.decode(encrypted_base64)?; - if encrypted.len() < 12 { - return Err(CryptoError::DecryptionError(aes_gcm::Error)); - } - let nonce_bytes = &encrypted[..12]; - let ciphertext = &encrypted[12..]; - let nonce = Nonce::from_slice(nonce_bytes); - let plaintext_bytes = cipher - .decrypt(nonce, ciphertext) - .map_err(CryptoError::DecryptionError)?; - let plaintext = String::from_utf8(plaintext_bytes) - .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; - Ok(plaintext) -} - -#[allow(dead_code)] -pub fn hash_it(input: &str) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(input.as_bytes()); - hasher.finalize().to_vec() -} - -#[allow(dead_code)] -pub fn hex_hash(input: &str) -> String { - let digest = hash_it(input); - digest.iter().map(|b| format!("{:02x}", b)).collect() -} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs deleted file mode 100644 index bb784e8..0000000 --- a/src/util/crypto_util.rs +++ /dev/null @@ -1,178 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, Payload}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; -use hkdf::Hkdf; -type HkdfSha256 = sha2::Sha256; -use sha2::{Digest, Sha256 as HashSha256}; -use x448::{PublicKey, Secret}; - -#[derive(Debug)] -#[allow(dead_code)] -pub enum SecurePayloadError { - InvalidBase64, - InvalidHex, - EncryptionError, - DecryptionError, - InvalidKeyLength, -} - -#[derive(Clone, Copy, Debug)] -#[allow(dead_code)] -pub enum DataFormat { - Raw, - Base64, - Hex, -} - -pub struct SecurePayload { - inner_data: Vec, - private_key: Secret, -} - -impl Clone for SecurePayload { - fn clone(&self) -> Self { - Self { - inner_data: self.inner_data.clone(), - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - } - } -} - -#[allow(dead_code)] -impl SecurePayload { - pub fn new>( - data: T, - format: DataFormat, - private_key: S, - ) -> Result - where - S: Into, - { - let raw_data = match format { - DataFormat::Raw => data.as_ref().to_vec(), - DataFormat::Base64 => BASE64_STD - .decode(data.as_ref()) - .map_err(|_| SecurePayloadError::InvalidBase64)?, - DataFormat::Hex => { - hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? - } - }; - - Ok(Self { - inner_data: raw_data, - private_key: private_key.into(), - }) - } - - pub fn get_public_key(&self) -> [u8; 56] { - *PublicKey::from(&self.private_key).as_bytes() - } - - pub fn export(&self, format: DataFormat) -> String { - match format.into() { - DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), - DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), - DataFormat::Hex => hex::encode(&self.inner_data), - } - } - - pub fn get_bytes(&self) -> &[u8] { - &self.inner_data - } - - pub fn get_hash(&self, format: DataFormat) -> String { - let mut hasher = HashSha256::new(); - hasher.update(&self.inner_data); - let result = hasher.finalize(); - - match format { - DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), - DataFormat::Base64 => BASE64_STD.encode(result), - DataFormat::Hex => hex::encode(result), - } - } - - pub fn encrypt_x448(&self, public_key: S) -> Result - where - S: Into, - { - let peer_pub = public_key.into(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; - - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let ciphertext = cipher - .encrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - Ok(SecurePayload { - inner_data: ciphertext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } - - pub fn decrypt_to_format( - &self, - peer_public_key_bytes: &[u8; 56], - output_format: DataFormat, - ) -> Result { - let decrypted_instance = - self.decrypt_x448(PublicKey::from_bytes(peer_public_key_bytes).unwrap())?; - Ok(decrypted_instance.export(output_format)) - } - - pub fn decrypt_x448( - &self, - peer_public_key_bytes: S, - ) -> Result - where - S: Into, - { - let peer_pub = peer_public_key_bytes.into(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let plaintext = cipher - .decrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - Ok(SecurePayload { - inner_data: plaintext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } -} diff --git a/src/util/file_util.rs b/src/util/file_util.rs deleted file mode 100644 index cd214b1..0000000 --- a/src/util/file_util.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::fs::{self, File}; -use std::io::{self, BufReader, Read}; -use std::path::{Path, PathBuf}; - -use crate::log; -use crate::util::logger::PrintType; - -#[allow(dead_code)] -pub fn delete_directory(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - delete_dir_recursive(&dir) -} - -#[allow(dead_code)] -fn delete_dir_recursive(directory: &Path) -> bool { - if !directory.exists() { - return false; - } - if let Err(e) = fs::remove_dir_all(directory) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't delete directory {}: {}", - directory.display(), - e, - ); - return false; - } - true -} - -#[allow(dead_code)] -pub fn delete_user_directory(user_id: i64) { - let user_dir = Path::new(&get_directory()) - .join("users") - .join(user_id.to_string()); - let _ = delete_dir_recursive(&user_dir); -} - -#[allow(dead_code)] -pub fn load_file_buf(path: &str, name: &str) -> io::Result> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - // Ensure the directory exists, create if necessary - if !dir.exists() { - if let Err(_) = fs::create_dir_all(&dir) { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "Directory creation failed", - )); - } - } - - // Create the file if it doesn't exist - if !file_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "File creation failed", - )); - } - - // Open the file and return a BufReader for efficient reading - let file = File::open(&file_path)?; - Ok(BufReader::new(file)) -} -#[allow(dead_code)] -pub fn has_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - if !dir.exists() { - return false; - } - - if !file_path.exists() { - return false; - } - - true -} -#[allow(dead_code)] -pub fn has_dir(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - - if !dir.exists() { - return false; - } - - true -} - -#[allow(dead_code)] -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) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't create directories: {}", - e - ); - return String::new(); - } - return String::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!( - 0, - PrintType::General, - "[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 load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - std::fs::read(file_path) -} - -#[allow(dead_code)] -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) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't create directories: {}", - e - ); - return; - } - } - - if let Err(e) = fs::write(&file_path, value) { - log!( - 0, - PrintType::General, - "[IMPORTANT] Couldn't write file {}: {}", - file_path.display(), - e - ); - } -} - -#[allow(dead_code)] -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() -} diff --git a/src/util/logger.rs b/src/util/logger.rs deleted file mode 100644 index 9e9a085..0000000 --- a/src/util/logger.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::{ - collections::BTreeMap, - fs::{self, OpenOptions}, - io::Write, - path::Path, - sync::{OnceLock, mpsc}, - thread, - time::{SystemTime, UNIX_EPOCH}, -}; - -use ansi_term::Color; -use ttp_core::{CommunicationValue, DataTypes, DataValue}; - -static LOGGER: OnceLock> = OnceLock::new(); - -#[allow(dead_code)] -#[derive(Clone, Copy)] -pub enum PrintType { - Call, - Client, - Iota, - Omikron, - Omega, - General, -} - -struct LogMessage { - timestamp_ms: u128, - sender: Option, - prefix: &'static str, - kind: PrintType, - is_error: bool, - message: String, -} - -pub fn startup() { - let (tx, rx) = mpsc::channel::(); - LOGGER.set(tx).expect("Logger already initialized"); - - thread::spawn(move || { - let log_dir = Path::new("logs"); - fs::create_dir_all(log_dir).expect("Failed to create log directory"); - - let start_ts = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs(); - - let path = log_dir.join(format!("log_{}.txt", start_ts)); - let mut file = OpenOptions::new() - .create(true) - .append(true) - .open(path) - .expect("Failed to open log file"); - - for msg in rx { - let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); - let sender = match msg.sender { - Some(id) => fixed_box(&id.to_string(), 19), - _ => fixed_box("", 19), - }; - - let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); - - println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); - - let _ = writeln!(file, "{}", line); - } - }); -} - -fn colorize(kind: PrintType, is_error: bool) -> Color { - if is_error { - return Color::Red; - } - - match kind { - PrintType::Call => Color::Purple, - PrintType::Client => Color::Green, - PrintType::Iota => Color::Yellow, - PrintType::Omikron => Color::Blue, - PrintType::Omega => Color::Cyan, - PrintType::General => Color::White, - } -} - -fn fixed_box(content: &str, width: usize) -> String { - let s: String = content.chars().take(width).collect(); - let len = s.chars().count(); - if len < width { - format!("[{}{}]", " ".repeat(width - len), s) - } else { - s - } -} - -pub fn log_internal( - sender: i64, - kind: PrintType, - prefix: &'static str, - is_error: bool, - message: String, -) { - let sender = if sender == 0 { None } else { Some(sender) }; - - if let Some(tx) = LOGGER.get() { - let _ = tx.send(LogMessage { - timestamp_ms: SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis(), - sender, - prefix, - kind, - is_error, - message, - }); - } -} -#[macro_export] -macro_rules! log { - ($sender: expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, "", false, format!($($arg)*)) - }; -} -#[macro_export] -macro_rules! log_in { - ($sender: expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, ">", false, format!($($arg)*)) - }; -} -#[macro_export] -macro_rules! log_out { - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, "<", false, format!($($arg)*)) - }; -} -#[macro_export] -macro_rules! log_err { - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal($sender, $kind, ">>", true, format!($($arg)*)) - }; -} - -// ******** COMMUNICATION VALUES ******** -pub fn log_cv_internal( - prefix: &'static str, - cv: &CommunicationValue, - print_type: Option, -) { - let formatted = format_cv(cv); - - log_internal( - cv.get_sender() as i64, - print_type.unwrap_or(PrintType::General), - prefix, - false, - formatted, - ); -} - -pub fn format_cv(cv: &CommunicationValue) -> String { - let mut parts = Vec::new(); - - let sender = cv.get_sender(); - let receiver = cv.get_receiver(); - - if sender > 0 && receiver > 0 { - parts.push(format!("{} > {}", sender, receiver)); - } else if sender > 0 { - parts.push(format!("{}", sender)); - } else if receiver > 0 { - parts.push(format!("> {}", receiver)); - } - - let comm_type = cv.get_type().to_string(); - parts.push(format!("{}", comm_type)); - - let data: &BTreeMap = cv.get_data_container(); - - let formated_data = - format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); - - parts.push(format!("{}", formated_data)); - - parts.join(": ") -} - -fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { - let parts: Vec = data - .into_iter() - .map(|(key, value)| { - let key_str = key.to_string(); - - match value { - DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), - - DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); - format!("{}={{ {} }}", key_str, inner_formatted) - } - - DataValue::Array(arr) => { - let arr_formatted = format_array(arr); - format!("{}=[{}]", key_str, arr_formatted) - } - - DataValue::Bool(b) => format!("{}={}", key_str, b), - - DataValue::BoolTrue => format!("{}=true", key_str), - DataValue::BoolFalse => format!("{}=false", key_str), - - DataValue::Number(num) => format!("{}={}", key_str, num), - - _ => "".to_string(), - } - }) - .collect(); - - parts.join(", ") -} - -fn format_array(arr: Vec) -> String { - let parts: Vec = arr - .into_iter() - .map(|value| match value { - DataValue::Str(s) => format!("\"{}\"", s), - - DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); - format!("{{ {} }}", inner_formatted) - } - - DataValue::Array(inner_arr) => { - let formatted = format_array(inner_arr); - format!("[{}]", formatted) - } - - DataValue::Bool(b) => b.to_string(), - - DataValue::BoolTrue => "true".to_string(), - DataValue::BoolFalse => "false".to_string(), - - DataValue::Number(num) => num.to_string(), - - _ => String::new(), - }) - .collect(); - - parts.join(", ") -} - -#[macro_export] -macro_rules! log_cv { - ($kind:expr, $cv:expr) => { - $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) - }; - ($cv:expr) => { - $crate::util::logger::log_cv_internal("", &$cv, None) - }; -} - -#[macro_export] -macro_rules! log_cv_in { - ($kind:expr, $cv:expr) => { - $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) - }; - ($cv:expr) => { - $crate::util::logger::log_cv_internal("> ", &$cv, None) - }; -} -#[macro_export] -macro_rules! log_cv_out { - ($kind:expr, $cv:expr) => { - $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) - }; - ($cv:expr) => { - $crate::util::logger::log_cv_internal("< ", &$cv, None) - }; -} diff --git a/src/util/mod.rs b/src/util/mod.rs deleted file mode 100644 index 1897223..0000000 --- a/src/util/mod.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod crypto_helper; -pub mod crypto_util; -pub mod file_util; -pub mod logger; From 50ca1077de6ed4b0f112363868846678cccef2c5 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 2 Apr 2026 22:28:41 +0200 Subject: [PATCH 103/220] Sync --- .gitignore | 29 + Cargo.lock | 4547 +++++++++++++++++++++++++++ Cargo.toml | 39 + LICENSE | 16 + README.md | 1 + src/main.rs | 65 + src/server/api.rs | 275 ++ src/server/mod.rs | 3 + src/server/server.rs | 67 + src/server/short_link.rs | 87 + src/sql/connection_status.rs | 30 + src/sql/mod.rs | 3 + src/sql/sql.rs | 765 +++++ src/sql/user_online_tracker.rs | 144 + src/transport/mod.rs | 2 + src/transport/omikron_connection.rs | 1139 +++++++ src/transport/omikron_manager.rs | 39 + src/util/crypto_helper.rs | 137 + src/util/crypto_util.rs | 201 ++ src/util/file_util.rs | 289 ++ src/util/logger.rs | 352 +++ src/util/mod.rs | 4 + 22 files changed, 8234 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100755 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/main.rs create mode 100644 src/server/api.rs create mode 100644 src/server/mod.rs create mode 100644 src/server/server.rs create mode 100644 src/server/short_link.rs create mode 100644 src/sql/connection_status.rs create mode 100644 src/sql/mod.rs create mode 100644 src/sql/sql.rs create mode 100644 src/sql/user_online_tracker.rs create mode 100644 src/transport/mod.rs create mode 100644 src/transport/omikron_connection.rs create mode 100644 src/transport/omikron_manager.rs create mode 100644 src/util/crypto_helper.rs create mode 100644 src/util/crypto_util.rs create mode 100644 src/util/file_util.rs create mode 100644 src/util/logger.rs create mode 100644 src/util/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3d273c --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# 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/ + + +.env +/logs + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1444c40 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4547 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Omega" +version = "0.1.0" +dependencies = [ + "actix-web", + "aes-gcm", + "ansi_term", + "anyhow", + "base64", + "dashmap", + "dotenv", + "epsilon-core", + "epsilon-native", + "hex", + "hkdf", + "json", + "once_cell", + "rand 0.8.5", + "rand_core 0.6.4", + "reqwest", + "rustls", + "rustls-pemfile", + "sha2", + "sqlx", + "strum 0.27.2", + "strum_macros 0.27.2", + "thiserror 2.0.18", + "tokio", + "uuid", + "x448", + "zip", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64", + "bitflags 2.11.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2 0.3.27", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "impl-more", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.3", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[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.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[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 = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[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" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +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 = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +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 = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "deflate64" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[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 = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[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" +dependencies = [ + "serde", +] + +[[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 = "epsilon-core" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" +dependencies = [ + "base64", + "byteorder", + "rand 0.8.5", + "strum 0.28.0", + "strum_macros 0.28.0", +] + +[[package]] +name = "epsilon-native" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" +dependencies = [ + "epsilon-core", + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "wtransport", +] + +[[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.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "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" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "httlib-huffman" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[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 1.4.0", + "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.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body", + "httparse", + "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 1.4.0", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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 = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +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 = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rust2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +dependencies = [ + "crc", + "sha2", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[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", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[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 = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "octets" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[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 = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + +[[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.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[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.5", +] + +[[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.5", +] + +[[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.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "aws-lc-rs", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "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.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[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.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "async-io 1.13.0", + "async-std", + "base64", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.4.1", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "async-std", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags 2.11.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64", + "bitflags 2.11.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +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 = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "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 = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http 1.4.0", + "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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[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.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[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 = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[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.2", +] + +[[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-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wtransport" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" +dependencies = [ + "bytes", + "pem", + "quinn", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "sha2", + "socket2 0.5.10", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "url", + "wtransport-proto", + "x509-parser 0.17.0", +] + +[[package]] +name = "wtransport-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" +dependencies = [ + "httlib-huffman", + "octets", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "x448" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" +dependencies = [ + "ed448-goldilocks", + "hex", + "rand_core 0.5.1", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "aws-lc-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +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" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100755 index 0000000..34673fb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "Omega" +version = "0.1.0" +edition = "2024" + +[dependencies] +epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } +epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } + +actix-web = { version = "4.12.1", features = ["rustls-0_23"] } +aes-gcm = "*" +ansi_term = "0.12.1" +anyhow = "1.0.101" +base64 = "0.22.1" +dashmap = "6.1.0" +dotenv = "0.15.0" +hex = "0.4.3" +hkdf = "0.12.4" +json = "0.12.4" +once_cell = "1.21.3" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +reqwest = { version = "0.13.2" } +rustls = { version = "0.23.37", default-features = false, features = [ + "std", + "tls12", + "aws-lc-rs", + "prefer-post-quantum", +] } +rustls-pemfile = "2.2.0" +sha2 = "0.10.9" +sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } +strum = "0.27.2" +strum_macros = "0.27.2" +tokio = { version = "*", features = ["full"] } +uuid = { version = "1.19.0", features = ["v4"] } +x448 = "0.6.0" +zip = "6.0.0" +thiserror = "2.0.18" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d218eff --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) [2025] [Methanium] +All rights reserved. + +This software is protected by copyright. Copying, editing, +distributing, publicly performing, or any other use of this software +or its components, in source or binary form, is strictly prohibited without the express +written permission of the copyright holder. + +FUTURE LICENSE ACCEPTANCE: +It is the copyright holder's intention to release this software in the future +under a license yet to be defined, which will, among other things, +allow private, non-commercial use. This statement does not constitute +a current license grant and does not alter the above +prohibition on use, copying, or modification. Until the formal +publication of such a future license, all rights remain +reserved. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e210a3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omega) ⚠️ diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8143c9f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,65 @@ +mod server; +mod sql; +mod transport; +mod util; + +use crate::sql::sql::initialize_db; +use crate::sql::sql::print_users; +use crate::transport::omikron_connection; +use crate::util::crypto_helper::load_public_key; +use crate::util::crypto_helper::load_secret_key; +use crate::util::logger::PrintType; +use crate::util::logger::startup; +use dotenv::dotenv; +use once_cell::sync::Lazy; +use rustls::crypto::aws_lc_rs::default_provider; +use std::env; + +static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); +pub fn get_private_key() -> x448::Secret { + load_secret_key(&*PRIVATE_KEY).unwrap() +} +static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); +pub fn get_public_key() -> x448::PublicKey { + load_public_key(&*PUBLIC_KEY).unwrap() +} + +#[tokio::main] +async fn main() { + if let Err(_) = default_provider().install_default() { + println!("Error loading Provider"); + return; + } + dotenv().ok(); + startup(); + log_in!("Incoming messages"); + log_out!("Outgoing messages"); + + tokio::spawn(async move { + match omikron_connection::start(9187).await { + Err(e) => log_err!(0, PrintType::General, "{:?}", e), + _ => {} + } + }); + + log!("Started"); + log!(" .env"); + if let Err(e) = initialize_db().await { + log!("[FATAL] Database initialization failed: {}", e); + log!( + "[FATAL] Please ensure the database is running and the .env file is configured correctly." + ); + return; + } else { + log!(" DB"); + } + if let Err(e) = print_users().await { + log!("[ERROR] Failed to print users: {}", e); + } else { + log!(" Users"); + } + + let _ = server::server::start(9188).await; + + tokio::signal::ctrl_c().await.unwrap(); +} diff --git a/src/server/api.rs b/src/server/api.rs new file mode 100644 index 0000000..eb6f9cf --- /dev/null +++ b/src/server/api.rs @@ -0,0 +1,275 @@ +use crate::get_public_key; +use crate::sql::sql; +use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; +use crate::transport::omikron_manager::get_random_omikron; +use crate::util::file_util::get_directory; +use crate::{ + sql::sql::{get_by_user_id, get_omikron_by_id}, + util::crypto_helper::public_key_to_base64, +}; +use actix_web::HttpResponse; +use actix_web::http::{StatusCode, header}; +use base64::Engine as _; +use json::JsonValue; + +pub async fn handle(path: &str, body_string: Option) -> HttpResponse { + if path == "OPTIONS" { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) + .insert_header(("Access-Control-Allow-Headers", "*")) + .finish(); + } + + let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); + + let _body: Option = if body_string.is_some() { + if let Ok(body_json) = json::parse(&body_string.unwrap()) { + Some(body_json) + } else { + None + } + } else { + None + }; + + let (status, body_text) = match path_parts.as_slice() { + // ================================================== + // DOWNLOAD IOTA FRONTEND + // ================================================== + ["api", "download", "iota_frontend"] => { + let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); + + match std::fs::read(file_path) { + Ok(file_bytes) => { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Content-Type", "application/zip")) + .insert_header(( + "Content-Disposition", + "attachment; filename=\"iota_frontend.zip\"", + )) + .body(file_bytes); + } + Err(_) => { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + return HttpResponse::NotFound() + .insert_header(("Access-Control-Allow-Origin", "*")) + .body(res.dump()); + } + } + } + + // ================================================== + // GET RANDOM OMIKRON + // ================================================== + ["api", "get", "omikron"] => { + if let Ok(omikron_conn) = get_random_omikron().await { + if let Some(id) = omikron_conn.get_omikron_id().await { + if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + + // ================================================== + // GET OMIKRON BY ID + // ================================================== + ["api", "get", "omikron", id] => { + let id = id.parse::().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await + { + if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + + // ================================================== + // GET ID BY USERNAME + // ================================================== + ["api", "get", "id", username] => { + if username.is_empty() { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok(( + id, + iota_id, + username, + _, + _, + _, + _, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) + } + } + + // ================================================== + // GET SERVER PUBLIC KEY + // ================================================== + ["api", "get", "public_key"] => { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["public_key"] = public_key_to_base64(&get_public_key()).into(); + (StatusCode::OK, res.dump()) + } + + // ================================================== + // GET USER BY ID + // ================================================== + ["api", "get", "user", id] => { + let id: i64 = id.parse().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok(( + id, + iota_id, + username, + display, + status_msg, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + if let Some(display) = display { + res["display"] = display.into(); + } + if let Some(status_msg) = status_msg { + res["status_message"] = status_msg.into(); + } + if let Some(about) = about { + res["about"] = about.into(); + } + if let Some(avatar) = avatar { + res["avatar"] = base64::engine::general_purpose::STANDARD + .encode(avatar) + .into(); + } + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) + } + } + + // ================================================== + // DEFAULT + // ================================================== + _ => { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + }; + let body_bytes = body_text.into_bytes(); + + HttpResponse::build(status) + .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) + .body(body_bytes) +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..8645e6e --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,3 @@ +pub mod api; +pub mod server; +pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs new file mode 100644 index 0000000..3618eb7 --- /dev/null +++ b/src/server/server.rs @@ -0,0 +1,67 @@ +use crate::{ + log, + server::{api, short_link::get_short_link}, + util::file_util::load_file_buf, +}; + +use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; + +use rustls::ServerConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls_pemfile::{certs, pkcs8_private_keys}; + +pub async fn start(port: u16) -> anyhow::Result<()> { + let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; + + let mut key_reader = load_file_buf("certs", "server_key.pem")?; + + let cert_chain: Vec> = + certs(&mut cert_reader).collect::>()?; + + let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) + .map(|res| res.map(Into::into)) + .collect::>()?; + + let key = keys.remove(0); + + let mut config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key)?; + + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + + let addr = format!("0.0.0.0:{port}"); + log!(" Server on {}", addr); + + HttpServer::new(move || { + App::new() + .route("/api/{path:.*}", web::to(api_handler)) + .route("/direct/{path:.*}", web::to(direct_handler)) + }) + .bind_rustls_0_23(addr, config)? + .run() + .await?; + + Ok(()) +} +async fn direct_handler(req: HttpRequest) -> impl Responder { + let path = req.uri().path().to_string(); + let short = path.replace("/direct/", ""); + + if let Ok(long) = get_short_link(&short).await { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, long)) + .finish() + } else { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, "https://tensamin.net")) + .finish() + } +} + +async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { + let path = req.uri().path().to_string(); + let body_string = String::from_utf8_lossy(&body).to_string(); + + api::handle(&path, Some(body_string)).await +} diff --git a/src/server/short_link.rs b/src/server/short_link.rs new file mode 100644 index 0000000..c0ab51d --- /dev/null +++ b/src/server/short_link.rs @@ -0,0 +1,87 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::{Rng, thread_rng}; + +static LINKS: Lazy> = Lazy::new(DashMap::new); + +const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; + +pub async fn add_short_link(long: &str) -> Result { + let raw = generate_unique_short_link().await; + LINKS.insert(raw.clone(), long.to_string()); + + Ok(format!( + "https://omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )) +} + +async fn generate_unique_short_link() -> String { + loop { + let short = generate_short_link().await; + if !LINKS.contains_key(&short) { + return short; + } + } +} + +pub async fn generate_short_link() -> String { + let len = short_length(); + + let mut rng = thread_rng(); + (0..len) + .map(|_| { + let idx = rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect() +} + +pub async fn get_short_link(short: &str) -> Result { + let key = if short.contains("/") { + short.split("/").nth(1).unwrap_or_default() + } else { + short + }; + let frag = short.replace(key, ""); + let normalized = normalize_short(&key); + + if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { + Ok(format!("{}{}", t, frag)) + } else { + Err(()) + } +} + +/* ---------------- helpers ---------------- */ + +fn short_length() -> usize { + let count = LINKS.len(); + + match count { + 0..=1_999 => 4, + 2_000..=999_999 => 8, + _ => 12, + } +} + +fn format_with_dashes(s: &str) -> String { + s.chars() + .collect::>() + .chunks(4) + .map(|c| c.iter().collect::()) + .collect::>() + .join("-") +} + +fn normalize_short(input: &str) -> String { + input + .chars() + .filter(|c| *c != '-') + .map(|c| match c { + 'Q' | 'O' => '0', + 'I' => 'l', + _ => c, + }) + .collect() +} diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs new file mode 100644 index 0000000..a175e6d --- /dev/null +++ b/src/sql/connection_status.rs @@ -0,0 +1,30 @@ +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] +#[allow(unused, non_camel_case_types)] +pub enum UserStatus { + user_offline, + user_online, + user_dnd, + user_idle, + user_wc, + user_borked, + iota_offline, + iota_online, + iota_borked, +} +#[allow(unused)] +impl UserStatus { + pub fn to_string(&self) -> String { + format!("{:?}", self) + } + pub fn from_str(s: &str) -> Option { + for sel in UserStatus::iter() { + if &sel.to_string() == s { + return Some(sel); + } + } + None + } +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs new file mode 100644 index 0000000..91ba841 --- /dev/null +++ b/src/sql/mod.rs @@ -0,0 +1,3 @@ +pub mod connection_status; +pub mod sql; +pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs new file mode 100644 index 0000000..8445e26 --- /dev/null +++ b/src/sql/sql.rs @@ -0,0 +1,765 @@ +use crate::log; +use once_cell::sync::Lazy; +use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{ + env, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; +use tokio::sync::RwLock; + +/* +use crate::sql::{ + iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, + sql::{ + change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, + change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, + get_register_id, register_complete_iota, register_complete_user, + }, +}; + */ + +static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); + +pub async fn connect() -> Result, sqlx::Error> { + let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); + let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); + let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); + + MySqlPoolOptions::new() + .max_connections(200) + .connect(&format!( + "mysql://{}:{}@127.0.0.1:3306/{}", + user, passwd, table + )) + .await +} +// Omega +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +pub async fn initialize_db() -> Result<(), sqlx::Error> { + let pool = connect().await?; + let mut db_lock = SQL_DB.write().await; + // create tables + // with indexes + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + users ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, + display VARCHAR(15) COLLATE utf8mb4_bin, + status VARCHAR(15) COLLATE utf8mb4_bin, + about VARCHAR(200) COLLATE utf8mb4_bin, + avatar MEDIUMBLOB, + sub_level INT(11) NOT NULL DEFAULT 0, + sub_end BIGINT(20) NOT NULL DEFAULT 0, + public_key TEXT NOT NULL COLLATE utf8mb4_bin, + private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', + iota_id BIGINT UNSIGNED NOT NULL, + token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + iotas ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + omikrons ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + notifications ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, + sender_id BIGINT UNSIGNED NOT NULL, + receiver_id BIGINT UNSIGNED NOT NULL, + amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + )", + ) + .execute(&pool) + .await; + *db_lock = Some(pool); + Ok(()) +} + +// ========================================================================================== +// REGISTER +// ========================================================================================== +pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); +pub static CURRENT_REGISTER_PROCESS: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); + +pub async fn get_register_id() -> u64 { + let mut current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + loop { + let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); + + if current_locked < current_time { + let result = CURRENT_MILLI_USED.compare_exchange( + current_locked, // expected value + current_time, // new value + Ordering::SeqCst, // acquire/release ordering + Ordering::SeqCst, // failure ordering + ); + + match result { + Ok(_) => { + CURRENT_REGISTER_PROCESS.write().await.push(current_time); + return current_time; + } + Err(_) => { + continue; + } + } + } else { + current_time = current_locked + 1; + } + } +} + +// ========================================================================================== +// USERS +// ========================================================================================== + +pub async fn get_by_username( + username: &str, +) -> Result< + ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", + ) + .bind(username) + .fetch_optional(&pool) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + _ => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_by_user_id( + id: i64, +) -> Result< + ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(&pool) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + _ => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_users_by_iota_id( + iota_id_param: i64, +) -> Result< + Vec<( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + )>, + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let rows = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", + ) + .bind(iota_id_param) + .fetch_all(&pool) + .await?; + + let mut users = Vec::new(); + for row in rows { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + users.push(( + id, + iota_id, + username, + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )); + } + + Ok(users) +} + +pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_username) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_display) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_avatar) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_about) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_status) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") + .bind(new_iota_id) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_keys( + id: i64, + new_public_key: String, + new_private_key_hash: String, +) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query( + "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_token) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn register_complete_user( + id: i64, + username: String, + public_key: String, + iota_id: i64, + token: String, +) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id) + .bind(username) + .bind(public_key) + .bind(iota_id) + .bind(token) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn print_users() -> Result<(), Box> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + log!("Printing users..."); + for row in sqlx::query( + "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", + ) + .fetch_all(&pool) + .await? + .iter() + { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + + log!( + "User: {:?}", + ( + id, + iota_id, + username, + display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), + status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), + about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), + sub_level, + sub_end + ) + ); + } + + Ok(()) +} + +// ========================================================================================== +// IOTA +// ========================================================================================== +pub async fn create_new_iota(public_key: String) -> Result { + let new_id = get_register_id().await as i64; + register_complete_iota(new_id, public_key).await?; + Ok(new_id) +} + +pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") + .bind(id) + .bind(public_key) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let result = sqlx::query_as::<_, (u64, Vec)>( + "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(&pool) + .await; + + match result { + Ok(optional_row) => match optional_row { + Some((id_u64, public_key)) => Ok(( + id_u64 as i64, + String::from_utf8_lossy(&public_key).to_string(), + )), + _ => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } +} + +pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_key) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +// ========================================================================================== +// OMIKRONS +// ========================================================================================== + +pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query_as::<_, (Vec, Vec)>( + "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(&pool) + .await?; + + match row { + Some((public_key, ip_address)) => Ok(( + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&ip_address).to_string(), + )), + _ => Err(sqlx::Error::RowNotFound), + } +} + +// ========================================================================================== +// PHI +// ========================================================================================== + +pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + INSERT INTO notifications (sender_id, receiver_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64)>( + r#" + SELECT sender_id, amount FROM notifications WHERE receiver_id = ? + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await +} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs new file mode 100644 index 0000000..42dfa22 --- /dev/null +++ b/src/sql/user_online_tracker.rs @@ -0,0 +1,144 @@ +use crate::sql; +use crate::sql::connection_status::UserStatus; +use dashmap::DashMap; +use once_cell::sync::Lazy; + +#[derive(Debug, Clone)] +pub struct UserConnection { + pub connection_type: UserStatus, + pub omikron_id: i64, +} + +// IotaID -> Primary OmikronID +static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); + +// IotaID -> Vec +static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); + +// UserID -> UserStatus +static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); + +pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { + let mut entry = IOTA_OMIKRON_CONNECTIONS + .entry(iota_id) + .or_insert_with(Vec::new); + + if !entry.contains(&omikron_id) { + entry.push(omikron_id); + } + + if primary { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); + } +} + +pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { + let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { + let mut vec = r.value().clone(); + vec.retain(|&id| id != omikron_id); + let empty = vec.is_empty(); + drop(r); + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); + empty + } else { + false + }; + + if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { + let primary_id = *primary_ref.value(); + drop(primary_ref); + if primary_id == omikron_id { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + } + + if connections_empty { + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + } + + connections_empty +} + +pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { + IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) +} + +pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { + IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) +} + +pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { + USER_STATUS_MAP.insert( + user_id, + UserConnection { + connection_type: status, + omikron_id, + }, + ); +} + +pub fn get_user_status(user_id: i64) -> Option { + USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) +} + +pub fn untrack_many_users(user_ids: &[i64]) { + for user_id in user_ids { + USER_STATUS_MAP.remove(user_id); + } +} + +pub async fn untrack_omikron(omikron_id: i64) { + let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION + .iter() + .filter(|entry| *entry.value() == omikron_id) + .map(|entry| *entry.key()) + .collect(); + + for key in primary_keys_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); + } + + let mut offline_iotas = Vec::new(); + let mut primary_to_remove = Vec::new(); + + // Collect iotas and primary info first + for r in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *r.key(); + let mut connections = r.value().clone(); + connections.retain(|&id| id != omikron_id); + + if connections.is_empty() { + offline_iotas.push(iota_id); + } + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(&iota_id) + .map(|p| *p == omikron_id) + .unwrap_or(false) + { + primary_to_remove.push(iota_id); + } + + // Update the connections vector after filtering + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); + } + + // Step 2: Remove primary connections safely + for iota_id in primary_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + + // Step 3: Remove users that were on this omikron + USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); + + // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP + for iota_id in offline_iotas { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { + for user in users { + USER_STATUS_MAP.remove(&user.0); + } + } + // Finally remove the empty connections vector + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + } +} diff --git a/src/transport/mod.rs b/src/transport/mod.rs new file mode 100644 index 0000000..4f3953a --- /dev/null +++ b/src/transport/mod.rs @@ -0,0 +1,2 @@ +pub mod omikron_connection; +pub mod omikron_manager; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs new file mode 100644 index 0000000..504d723 --- /dev/null +++ b/src/transport/omikron_connection.rs @@ -0,0 +1,1139 @@ +use crate::{ + get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, + server::short_link::add_short_link, + sql::{ + connection_status::UserStatus, + sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, + user_online_tracker::{self}, + }, + transport::omikron_manager, + util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use dashmap::DashMap; +use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use epsilon_native::{Host, Receiver, Sender}; +use rand::{Rng, distributions::Alphanumeric}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::{ + sync::{Mutex, RwLock}, + time::interval, +}; +use x448::PublicKey; + +// ============================================================================ +// Configuration +// ============================================================================ + +const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); +const MAX_WAITING_AGE: Duration = Duration::from_secs(60); + +// ============================================================================ +// Error Types +// ============================================================================ + +#[derive(Debug, thiserror::Error)] +pub enum OmikronError { + #[error("Not connected")] + NotConnected, + #[error("Not authenticated")] + NotAuthenticated, + #[error("Invalid response")] + InvalidResponse, + #[error("Authentication failed")] + AuthenticationFailed, + #[error("SQL error: {0}")] + Sql(String), + #[error("Send error: {0}")] + Send(String), +} + +pub type OmikronResult = Result; + +// ============================================================================ +// Waiting Task System (Preserved from original) +// ============================================================================ + +pub struct WaitingTask { + pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub inserted_at: Instant, +} + +// ============================================================================ +// Connection State +// ============================================================================ + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum AuthState { + Unauthenticated, + Identified { omikron_id: i64 }, + Authenticated { omikron_id: i64 }, +} + +impl AuthState { + fn is_authenticated(&self) -> bool { + match self { + AuthState::Authenticated { omikron_id } => true, + _ => false, + } + } + + fn omikron_id(&self) -> Option { + match self { + AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { + Some(*omikron_id) + } + _ => None, + } + } +} + +// ============================================================================ +// Omikron Connection (Epsilon/QUIC-based) +// ============================================================================ + +pub struct OmikronConnection { + id: u64, + sender: Mutex>, + state: RwLock, + challenge: RwLock, + pub_key: RwLock>>, + pub ping: RwLock, + waiting_tasks: DashMap, + cleanup_handle: Mutex>>, +} + +impl OmikronConnection { + // ------------------------------------------------------------------------- + // Construction + // ------------------------------------------------------------------------- + + pub fn new(sender: Sender) -> Arc { + let conn = Arc::new(Self { + id: rand::random(), + sender: Mutex::new(Some(sender)), + state: RwLock::new(AuthState::Unauthenticated), + challenge: RwLock::new(String::new()), + pub_key: RwLock::new(None), + ping: RwLock::new(-1), + waiting_tasks: DashMap::new(), + cleanup_handle: Mutex::new(None), + }); + + conn + } + + // ------------------------------------------------------------------------- + // Main Handler Loop + // ------------------------------------------------------------------------- + + pub async fn handle(self: Arc, receiver: &mut Receiver) { + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection started" + ); + + // Start cleanup task + let cleanup_conn = self.clone(); + let cleanup_handle = tokio::spawn(async move { + let mut ticker = interval(CLEANUP_INTERVAL); + loop { + ticker.tick().await; + cleanup_conn + .waiting_tasks + .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + } + }); + *self.cleanup_handle.lock().await = Some(cleanup_handle); + + while let Ok(cv) = receiver.receive().await { + if let Err(e) = self.clone().process_message(cv).await { + log_err!(0, PrintType::Omega, "Error processing message: {}", e); + if matches!(e, OmikronError::NotConnected) { + break; + } + } + } + + self.clone().cleanup().await; + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection closed" + ); + } + + // ------------------------------------------------------------------------- + // Message Processing + // ------------------------------------------------------------------------- + + async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_in!(PrintType::Omikron, &cv); + } + + let msg_id = cv.get_id(); + + // Check waiting tasks first (response to previous request) + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + let _ = (task.task)(self.clone(), cv); + return Ok(()); + } + + // Handle ping regardless of auth state + if cv.is_type(CommunicationType::ping) { + return self.handle_ping(cv).await; + } + + let current_state = *self.state.read().await; + // Route based on authentication state + match current_state { + AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, + AuthState::Identified { .. } => self.clone().handle_identified(cv).await, + AuthState::Authenticated { omikron_id } => { + self.clone().handle_authenticated(cv, omikron_id).await + } + } + } + + // ------------------------------------------------------------------------- + // Authentication Handlers + // ------------------------------------------------------------------------- + + async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::identification) { + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); + } + + // Extract omikron ID + let omikron_id = cv + .get_data(DataTypes::omikron_id) + .as_number() + .ok_or(OmikronError::InvalidResponse)?; + log!("Omikron {:?} connected", omikron_id); + + // Lookup omikron in database + let (public_key, _) = get_omikron_by_id(omikron_id) + .await + .map_err(|e| OmikronError::Sql(e.to_string()))?; + + log!("Got public Key"); + + let pub_key_bytes = STANDARD + .decode(&public_key) + .map_err(|_| OmikronError::AuthenticationFailed)?; + + let pub_key_bytes_clone = pub_key_bytes.clone(); + let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) + .ok_or(OmikronError::AuthenticationFailed)?; + + log!("Decoded public Key"); + + // Generate challenge + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + log!("Generated Challenge"); + + *self.challenge.write().await = challenge.clone(); + + log!("Stored Challenge"); + + *self.pub_key.write().await = Some(pub_key_bytes); + + log!("Stored Pubkey"); + + *self.state.write().await = AuthState::Identified { omikron_id }; + + log!("Stored State"); + + let challenge_clone = challenge.clone(); + let private_key = get_private_key(); + let public_key_for_encrypt = omikron_pub_key; + + let encrypted = tokio::task::spawn_blocking(move || { + encrypt(private_key, public_key_for_encrypt, &challenge_clone) + .map_err(|_| OmikronError::AuthenticationFailed) + }) + .await + .map_err(|_| OmikronError::AuthenticationFailed)??; + + log!("Encrypted Challenge"); + + // Send challenge response + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + + log!("Sending Challenge"); + self.send(&response).await + } + + async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::challenge_response) { + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); + } + + let client_response = cv + .get_data(DataTypes::challenge) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; + + let expected_challenge = self.challenge.read().await.clone(); + + if client_response == expected_challenge { + let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); + *self.state.write().await = AuthState::Authenticated { omikron_id }; + + omikron_manager::add_omikron(self.clone()).await; + + let response = CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, DataValue::Bool(true)); + + self.clone().send(&response).await?; + log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); + Ok(()) + } else { + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + .await; + Err(OmikronError::AuthenticationFailed) + } + } + + // ------------------------------------------------------------------------- + // Authenticated Message Handlers + // ------------------------------------------------------------------------- + + async fn handle_authenticated( + self: Arc, + cv: CommunicationValue, + omikron_id: i64, + ) -> OmikronResult<()> { + match cv.get_type() { + // Link shortening + CommunicationType::shorten_link => self.handle_shorten_link(cv).await, + + // Online status tracking + CommunicationType::user_connected => { + self.handle_user_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::user_disconnected => { + self.handle_user_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_connected => { + self.handle_iota_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_disconnected => { + self.handle_iota_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::sync_client_iota_status => { + self.handle_sync_status(cv, omikron_id).await; + Ok(()) + } + + CommunicationType::get_user_data => self.handle_get_user_data(cv).await, + CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, + + CommunicationType::get_register => self.handle_get_register(cv).await, + CommunicationType::complete_register_iota => { + self.handle_complete_register_iota(cv).await + } + CommunicationType::complete_register_user => { + self.handle_complete_register_user(cv).await + } + + CommunicationType::change_user_data => self.handle_change_user_data(cv).await, + CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, + CommunicationType::delete_user => self.handle_delete_user(cv).await, + CommunicationType::delete_iota => self.handle_delete_iota(cv).await, + + CommunicationType::get_notifications => self.handle_get_notifications(cv).await, + CommunicationType::read_notification => self.handle_read_notification(cv).await, + CommunicationType::push_notification => self.handle_push_notification(cv).await, + + _ => { + log_err!( + 0, + PrintType::Omega, + "Unknown message type: {:?}", + cv.get_type() + ); + Ok(()) + } + } + } + + // ------------------------------------------------------------------------- + // Specific Handlers (ported from original WebSocket implementation) + // ------------------------------------------------------------------------- + + async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let link = cv + .get_data(DataTypes::link) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; + + let short = add_short_link(link) + .await + .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; + + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, DataValue::Str(short)); + + self.send(&response).await + } + + async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "User connected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_online, + omikron_id, + ); + } + } + + async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { + log_in!(PrintType::Omega, "User disconnected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_offline, + status.omikron_id, + ); + } + } + } + + async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA connected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + + let mut user_ids = Vec::new(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { + user_ids.push(DataValue::Number(user_id)); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_offline, + omikron_id, + ); + } + } else { + log_in!(PrintType::General, "SQL error loading users for IOTA"); + } + + let response = CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + + let _ = self.send(&response).await; + } else { + log_in!(PrintType::General, "No IOTA ID found"); + } + } + + async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA disconnected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); + } + } + } + } + + async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { + if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { + for user_id_val in user_ids { + if let DataValue::Number(user_id) = user_id_val { + user_online_tracker::track_user_status( + *user_id, + UserStatus::user_online, + omikron_id, + ); + } + } + } + + if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { + for iota_id_val in iota_ids { + if let DataValue::Number(iota_id) = iota_id_val { + user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); + } + } + } + } + + async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + // Try by user_id first + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok(user_data) = get_by_user_id(user_id as i64).await { + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; + return self.send(&response).await; + } + } + + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok(user_data) = get_by_username(username).await { + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; + return self.send(&response).await; + } + } + + // Not found + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_user_data_response( + self: Arc, + msg_id: u32, + user: ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + ) -> CommunicationValue { + let ( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + ) = user; + + let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(msg_id) + .add_data(DataTypes::username, DataValue::Str(username.clone())) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::user_id, DataValue::Number(id)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) + .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) + .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); + + // Display name (fallback to username) + let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); + response = response.add_data(DataTypes::display, DataValue::Str(display_name)); + + // Optional fields + if let Some(s) = status.filter(|s| !s.is_empty()) { + response = response.add_data(DataTypes::status, DataValue::Str(s)); + } + if let Some(a) = about.filter(|a| !a.is_empty()) { + response = response.add_data(DataTypes::about, DataValue::Str(a)); + } + if let Some(av) = avatar { + response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); + } + + // Online status + let user_status = user_online_tracker::get_user_status(id); + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + if let Some(us) = user_status { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(us.connection_type.to_string()), + ); + response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); + } else { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(UserStatus::iota_offline.to_string()), + ); + } + + response = response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ); + + response + } + + async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + // Try by iota_id + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .clone() + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; + } + } + + // Try by user_id + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id as i64).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .clone() + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id as i64), + None, + ) + .await; + return self.send(&response).await; + } + } + } + + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_username(username).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .clone() + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id), + Some(username.to_string()), + ) + .await; + return self.send(&response).await; + } + } + } + + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_iota_data_response( + self: Arc, + msg_id: u32, + iota_id: i64, + public_key: String, + user_id: Option, + username: Option, + ) -> CommunicationValue { + let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(msg_id) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + + if let Some(uid) = user_id { + response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); + } + if let Some(uname) = username { + response = response.add_data(DataTypes::username, DataValue::Str(uname)); + } + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ) + } + + async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let register_id = sql::get_register_id().await; + let response = CommunicationValue::new(CommunicationType::get_register) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); + self.send(&response).await + } + + async fn handle_complete_register_iota( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let iota_id_opt = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + + if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { + if let Some(iota_id) = iota_id_opt { + // Register existing IOTA + match sql::register_complete_iota(iota_id, public_key.to_string()).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + // Create new IOTA + match sql::create_new_iota(public_key.to_string()).await { + Ok(new_iota_id) => { + let response = + CommunicationValue::new(CommunicationType::complete_register_iota) + .with_id(cv.get_id()) + .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_complete_register_user( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv + .get_data(DataTypes::user_id) + .as_number() + .map(|n| n as i64); + let username = cv + .get_data(DataTypes::username) + .as_str() + .map(|s| s.to_string()); + let public_key = cv + .get_data(DataTypes::public_key) + .as_str() + .map(|s| s.to_string()); + let iota_id = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + let reset_token = cv + .get_data(DataTypes::reset_token) + .as_str() + .map(|s| s.to_string()); + + if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = + (user_id, username, public_key, iota_id, reset_token) + { + match sql::register_complete_user(uid, uname, pk, iid, rt).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + let mut success = true; + let mut error_message = String::new(); + + // Process each field + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(display) = cv.get_data(DataTypes::display).as_str() { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(about) = cv.get_data(DataTypes::about).as_str() { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(status) = cv.get_data(DataTypes::status).as_str() { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let (Some(public_key), Some(private_key_hash)) = ( + cv.get_data(DataTypes::public_key).as_str(), + cv.get_data(DataTypes::private_key_hash).as_str(), + ) { + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), + ) + .await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } + + async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + + if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64), + cv.get_data(DataTypes::reset_token).as_str(), + cv.get_data(DataTypes::new_token).as_str(), + ) { + match sql::get_by_user_id(user_id).await { + Ok(user) => { + let current_token = user.11; // reset_token field + if current_token == reset_token { + let mut success = true; + let mut error_message = String::new(); + + if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + success = false; + error_message = e.to_string(); + } + if success { + if let Err(e) = sql::change_token(user_id, new_token.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } else { + self.send_error_response( + cv.get_id(), + CommunicationType::error_invalid_challenge, + ) + .await + } + } + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error_not_found) + .await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + match sql::delete_user(user_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + + async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if let Some(iota_id) = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64) + { + match sql::delete_iota(iota_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_get_notifications( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Ok(notifications) = sql::get_notifications(user_id).await { + let json_array: Vec = notifications + .into_iter() + .map(|(sender, amount)| { + DataValue::Container(vec![ + (DataTypes::sender_id, DataValue::Number(sender)), + (DataTypes::amount, DataValue::Number(amount)), + ]) + }) + .collect(); + + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_data(DataTypes::notifications, DataValue::Array(json_array)); + self.send(&response).await + } else { + Ok(()) + } + } + + async fn handle_read_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::read_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_push_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::add_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + *self.ping.write().await = *last_ping; + } + + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + self.send(&response).await + } + + // ------------------------------------------------------------------------- + // Utilities + // ------------------------------------------------------------------------- + + async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Omikron, cv); + } + + let guard = self.sender.lock().await; + let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; + + sender + .send(cv) + .await + .map_err(|e| OmikronError::Send(e.to_string())) + } + + async fn send_error_response( + self: Arc, + message_id: u32, + error_type: CommunicationType, + ) -> OmikronResult<()> { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send(&error).await + } + + pub async fn close(self: Arc) { + log_in!( + self.get_omikron_id().await.unwrap_or(0), + PrintType::Omega, + "Omikron connection Closed" + ); + } + + async fn cleanup(self: Arc) { + if let Some(omikron_id) = self.state.read().await.omikron_id() { + if omikron_id != 0 { + log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; + } + } + + // Cancel cleanup task + if let Some(handle) = self.cleanup_handle.lock().await.take() { + handle.abort(); + } + } + + // Public API for external use + pub async fn is_authenticated(self: Arc) -> bool { + self.state.read().await.is_authenticated() + } + + pub async fn get_omikron_id(self: Arc) -> Option { + self.state.read().await.omikron_id() + } + + pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { + self.send(cv).await + } +} + +// ============================================================================ +// Server Startup +// ============================================================================ + +pub async fn start(port: u16) -> Result<(), Box> { + let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); + + let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); + + let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; + log!("OmikronServer listening on port {}", port); + + while let Some((sender, mut receiver)) = host.next().await { + tokio::spawn(async move { + let conn = OmikronConnection::new(sender); + conn.handle(&mut receiver).await; + }); + } + + Ok(()) +} diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs new file mode 100644 index 0000000..fbee4a9 --- /dev/null +++ b/src/transport/omikron_manager.rs @@ -0,0 +1,39 @@ +use crate::transport::omikron_connection::OmikronConnection; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::prelude::IteratorRandom; +use std::sync::Arc; + +pub static OMIKRON_CONNECTIONS: Lazy>> = + Lazy::new(|| DashMap::new()); + +pub async fn add_omikron(conn: Arc) { + let id = match conn.clone().get_omikron_id().await { + Some(id) => id, + _ => { + conn.close().await; + return; + } + }; + + if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { + old.close().await; + } +} + +pub async fn remove_omikron(omikron_id: i64) { + OMIKRON_CONNECTIONS.remove(&omikron_id); +} +pub async fn get_random_omikron() -> Result, ()> { + let mut rng = rand::thread_rng(); + + let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); + + if let Some(key) = keys.into_iter().choose(&mut rng) { + if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { + return Ok(entry.clone()); + } + } + + Err(()) +} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs new file mode 100644 index 0000000..68e3855 --- /dev/null +++ b/src/util/crypto_helper.rs @@ -0,0 +1,137 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, OsRng}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use rand_core::RngCore; +use sha2::{Digest, Sha256}; +use x448::{PublicKey, Secret, SharedSecret}; + +/// Errors for crypto operations +#[derive(Debug)] +pub enum CryptoError { + Base64Decode, + AgreementError, + EncryptionError(aes_gcm::Error), + DecryptionError(aes_gcm::Error), +} + +impl From for CryptoError { + fn from(_: base64::DecodeError) -> Self { + CryptoError::Base64Decode + } +} + +pub fn generate_keypair() -> (Secret, PublicKey) { + let mut buf = [0u8; 56]; + let mut rng = OsRng; + rng.fill_bytes(&mut buf); + let secret = Secret::from_bytes(&buf).unwrap(); + let public = PublicKey::from(&secret); + (secret, public) +} + +pub fn public_key_to_base64(pubkey: &PublicKey) -> String { + STANDARD.encode(pubkey.as_bytes().as_ref()) +} + +pub fn secret_key_to_base64(secret: &Secret) -> String { + STANDARD.encode(secret.as_bytes().as_ref()) +} + +pub fn load_public_key(base64_pub: &str) -> Option { + let bytes = STANDARD.decode(base64_pub).unwrap(); + PublicKey::from_bytes(&bytes) +} + +pub fn load_secret_key(base64_secret: &str) -> Option { + let bytes = STANDARD.decode(base64_secret).unwrap(); + Secret::from_bytes(&bytes) +} + +fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(shared.as_bytes()); + let result = hasher.finalize(); + let mut key = [0u8; 32]; + key.copy_from_slice(&result[..32]); + key +} + +pub fn encrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + plaintext: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + encrypt(secret, peer_pub, plaintext) +} +pub fn encrypt( + secret: Secret, + peer_pub: PublicKey, + plaintext: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + let mut nonce_bytes = [0u8; 12]; + OsRng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + let ciphertext = cipher + .encrypt(nonce, plaintext.as_bytes()) + .map_err(CryptoError::EncryptionError)?; + // prefix nonce to ciphertext + let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); + out.extend_from_slice(&nonce_bytes); + out.extend_from_slice(&ciphertext); + Ok(STANDARD.encode(&out)) +} + +pub fn decrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + encrypted_base64: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + decrypt(secret, peer_pub, encrypted_base64) +} +pub fn decrypt( + secret: Secret, + peer_pub: PublicKey, + encrypted_base64: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + + let encrypted = STANDARD.decode(encrypted_base64)?; + if encrypted.len() < 12 { + return Err(CryptoError::DecryptionError(aes_gcm::Error)); + } + let nonce_bytes = &encrypted[..12]; + let ciphertext = &encrypted[12..]; + let nonce = Nonce::from_slice(nonce_bytes); + let plaintext_bytes = cipher + .decrypt(nonce, ciphertext) + .map_err(CryptoError::DecryptionError)?; + let plaintext = String::from_utf8(plaintext_bytes) + .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; + Ok(plaintext) +} + +pub fn hash_it(input: &str) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(input.as_bytes()); + hasher.finalize().to_vec() +} + +pub fn hex_hash(input: &str) -> String { + let digest = hash_it(input); + digest.iter().map(|b| format!("{:02x}", b)).collect() +} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs new file mode 100644 index 0000000..97acab4 --- /dev/null +++ b/src/util/crypto_util.rs @@ -0,0 +1,201 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, Payload}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; +use hkdf::Hkdf; +use sha2::{Digest, Sha256}; +use x448::{PublicKey, Secret}; + +// --- Custom Errors --- +#[derive(Debug)] +pub enum SecurePayloadError { + InvalidBase64, + InvalidHex, + EncryptionError, + DecryptionError, + InvalidKeyLength, +} + +// --- Data Format Enum --- +#[derive(Clone, Copy, Debug)] +pub enum DataFormat { + Raw, + Base64, + Hex, +} + +// --- Main Class Structure --- +pub struct SecurePayload { + /// The internal canonical representation is always raw bytes. + inner_data: Vec, + /// The private key of the user associated with this payload instance. + private_key: Secret, +} + +impl Clone for SecurePayload { + fn clone(&self) -> Self { + Self { + inner_data: self.inner_data.clone(), + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + } + } +} + +impl SecurePayload { + /// Clear Constructor: Takes data in any format and the user's private key. + pub fn new>( + data: T, + format: DataFormat, + private_key: S, + ) -> Result + where + S: Into, + { + let raw_data = match format { + DataFormat::Raw => data.as_ref().to_vec(), + DataFormat::Base64 => BASE64_STD + .decode(data.as_ref()) + .map_err(|_| SecurePayloadError::InvalidBase64)?, + DataFormat::Hex => { + hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? + } + }; + + Ok(Self { + inner_data: raw_data, + private_key: private_key.into(), + }) + } + + /// Helper to get the public key associated with this instance's private key. + pub fn get_public_key(&self) -> [u8; 56] { + *PublicKey::from(&self.private_key).as_bytes() + } + + /// Exports the internal data to the requested format + pub fn export(&self, format: DataFormat) -> String { + match format.into() { + DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), + DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), + DataFormat::Hex => hex::encode(&self.inner_data), + } + } + + /// Access raw bytes directly + pub fn get_bytes(&self) -> &[u8] { + &self.inner_data + } + + /// Returns the SHA-256 Hash of the data in the requested format + pub fn get_hash(&self, format: DataFormat) -> String { + let mut hasher = Sha256::new(); + hasher.update(&self.inner_data); + let result = hasher.finalize(); + + match format { + DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), + DataFormat::Base64 => BASE64_STD.encode(result), + DataFormat::Hex => hex::encode(result), + } + } + + /// Encrypts the held data for a specific recipient using AES-256-GCM. + /// The message will contain ONLY the ciphertext. + pub fn encrypt_x448(&self, public_key: S) -> Result + where + S: Into, + { + let peer_pub = public_key.into(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + println!( + "Encryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 3. Key & Nonce Derivation (HKDF) + // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 4. Encrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let ciphertext = cipher + .encrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + // 5. Result is ONLY the ciphertext. No key or nonce is packed. + Ok(SecurePayload { + inner_data: ciphertext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } + + /// Decrypts the held data providing the sender's public key manually. + pub fn decrypt_to_format( + &self, + peer_public_key_bytes: &[u8; 56], + output_format: DataFormat, + ) -> Result { + let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; + Ok(decrypted_instance.export(output_format)) + } + + /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. + pub fn decrypt_x448( + &self, + peer_public_key_bytes: &[u8; 56], + ) -> Result { + // 1. Perform Exchange + let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + // LOGGING: Shared Secret + println!( + "Decryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 2. Key & Nonce Derivation (Must match encryption exactly) + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 3. Decrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let plaintext = cipher + .decrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + Ok(SecurePayload { + inner_data: plaintext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } +} diff --git a/src/util/file_util.rs b/src/util/file_util.rs new file mode 100644 index 0000000..b108810 --- /dev/null +++ b/src/util/file_util.rs @@ -0,0 +1,289 @@ +use std::fs::{self, File}; +use std::io::{self, BufReader, Read}; +use std::path::{Path, PathBuf}; +use tokio::io::AsyncWriteExt; +use uuid::Uuid; +use zip::ZipArchive; + +use crate::log; + +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() +} + +#[allow(dead_code)] +pub fn delete_directory(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + delete_dir_recursive(&dir) +} + +#[allow(dead_code)] +fn delete_dir_recursive(directory: &Path) -> bool { + if !directory.exists() { + return false; + } + if let Err(e) = fs::remove_dir_all(directory) { + log!( + "[IMPORTANT] Couldn't delete directory {}: {}", + directory.display(), + e + ); + return false; + } + true +} + +#[allow(dead_code)] +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_buf(path: &str, name: &str) -> io::Result> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + // Ensure the directory exists, create if necessary + if !dir.exists() { + if let Err(_) = fs::create_dir_all(&dir) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Directory creation failed", + )); + } + } + + // Create the file if it doesn't exist + if !file_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "File creation failed", + )); + } + + // Open the file and return a BufReader for efficient reading + let file = File::open(&file_path)?; + Ok(BufReader::new(file)) +} +pub fn has_file(path: &str, name: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + return false; + } + + if !file_path.exists() { + return false; + } + + true +} +pub fn has_dir(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + + if !dir.exists() { + return false; + } + + true +} + +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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return String::new(); + } + return String::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log!("[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 load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + std::fs::read(file_path) +} + +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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return; + } + } + + if let Err(e) = fs::write(&file_path, value) { + log!( + "[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() +} + +// Helper to download the zip file content to a file on disk +#[allow(dead_code)] +async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { + let response = reqwest::get(url).await?; + + // Check for successful response status + if !response.status().is_success() { + return Err(format!("Failed to download file: Status {}", response.status()).into()); + } + + let mut zip_file = tokio::fs::File::create(as_name).await?; + let body = response.bytes().await?; + zip_file.write_all(&body).await?; + + Ok(()) +} + +#[allow(dead_code, deprecated)] +fn extract_zip_contents_to_folder( + zip_path: &Path, + target_dir: &Path, +) -> Result<(), Box> { + let file = File::open(zip_path)?; + let mut archive = ZipArchive::new(file)?; + + let staging_dir = target_dir.with_extension("staging"); + + let _ = fs::remove_dir_all(&staging_dir); + fs::create_dir_all(&staging_dir)?; + + let mut first_item_name: Option = None; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let entry_path = staging_dir.join(file.sanitized_name()); + + if i == 0 { + if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { + first_item_name = Some(file.sanitized_name()); + } + } + + if file.name().ends_with('/') { + fs::create_dir_all(&entry_path)?; + } else { + if let Some(parent) = entry_path.parent() { + fs::create_dir_all(parent)?; + } + let mut out_file = File::create(entry_path)?; + io::copy(&mut file, &mut out_file)?; + } + } + + if let Some(root_path) = first_item_name { + let root_dir = staging_dir.join(&root_path); + + if root_dir.is_dir() { + let root_contents_count = fs::read_dir(&staging_dir)?.count(); + + if root_contents_count == 1 + || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) + { + let _ = fs::remove_dir_all(target_dir); + fs::create_dir_all(target_dir)?; + + for entry in fs::read_dir(root_dir)? { + let entry = entry?; + let src = entry.path(); + let dest = target_dir.join(entry.file_name()); + + if let Err(_) = fs::rename(&src, &dest) { + if src.is_file() { + fs::copy(&src, &dest)?; + } else { + if entry.path().is_dir() { + fs::rename(&src, &dest)?; + } + } + } + } + + let _ = fs::remove_dir_all(&staging_dir); + return Ok(()); + } + } + } + + log!("Extracting directly (no single root folder detected)."); + let _ = fs::remove_dir_all(target_dir); + fs::rename(&staging_dir, target_dir)?; + + Ok(()) +} + +#[allow(dead_code)] +pub async fn download_and_extract_zip(url: &str, as_name: &str) { + let base_dir = PathBuf::from(get_directory()); + let zip_filename = format!("{}.zip", Uuid::new_v4()); + let zip_path = base_dir.join(&zip_filename); + let target_dir = base_dir.join(as_name); + + if let Err(e) = download_zip(url, &zip_path).await { + log!("Error downloading file: {}", e); + return; + } + + let zip_path_clone = zip_path.clone(); + let target_dir_clone = target_dir.clone(); + let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone); + if let Err(e) = extract_result { + log!("Panic during ZIP extraction: {}", e); + } + + if let Err(e) = tokio::fs::remove_file(&zip_path).await { + log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs new file mode 100644 index 0000000..d1a7764 --- /dev/null +++ b/src/util/logger.rs @@ -0,0 +1,352 @@ +use std::{ + collections::{BTreeMap, HashMap}, + fs::{self, OpenOptions}, + io::Write, + path::Path, + sync::{OnceLock, mpsc}, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; + +use ansi_term::Color; +use epsilon_core::{CommunicationValue, DataTypes, DataValue}; +use json::JsonValue; + +static LOGGER: OnceLock> = OnceLock::new(); + +#[derive(Clone, Copy)] +#[allow(unused)] +pub enum PrintType { + Call, + Client, + Iota, + Omikron, + Omega, + General, +} + +struct LogMessage { + timestamp_ms: u128, + sender: Option, + prefix: &'static str, + kind: PrintType, + is_error: bool, + message: String, +} + +pub fn startup() { + let (tx, rx) = mpsc::channel::(); + LOGGER.set(tx).expect("Logger already initialized"); + + thread::spawn(move || { + let log_dir = Path::new("logs"); + fs::create_dir_all(log_dir).expect("Failed to create log directory"); + + let start_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let path = log_dir.join(format!("log_{}.txt", start_ts)); + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .expect("Failed to open log file"); + + for msg in rx { + let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); + let sender = match msg.sender { + Some(id) => fixed_box(&id.to_string(), 19), + _ => fixed_box("", 19), + }; + + let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); + + println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); + + let _ = writeln!(file, "{}", line); + } + }); +} + +fn colorize(kind: PrintType, is_error: bool) -> Color { + if is_error { + return Color::Red; + } + + match kind { + PrintType::Call => Color::Purple, + PrintType::Client => Color::Green, + PrintType::Iota => Color::Yellow, + PrintType::Omikron => Color::Blue, + PrintType::Omega => Color::Cyan, + PrintType::General => Color::White, + } +} + +fn fixed_box(content: &str, width: usize) -> String { + let s: String = content.chars().take(width).collect(); + let len = s.chars().count(); + if len < width { + format!("[{}{}]", " ".repeat(width - len), s) + } else { + s + } +} + +pub fn log_internal( + sender: Option, + kind: PrintType, + prefix: &'static str, + is_error: bool, + message: String, +) { + if let Some(tx) = LOGGER.get() { + let _ = tx.send(LogMessage { + timestamp_ms: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + sender, + prefix, + kind, + is_error, + message, + }); + } +} + +#[macro_export] +macro_rules! log { + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + "", + false, + format!($($arg)*) + ) + }; + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! log_in { + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + ">", + false, + format!($($arg)*) + ) + }; +} + +#[macro_export] +macro_rules! log_out { + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + "<", + false, + format!($($arg)*) + ) + }; +} + +#[macro_export] +macro_rules! log_err { + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + ">>", + true, + format!($($arg)*) + ) + }; +} + +// ******** COMMUNICATION VALUES ******** +pub fn log_cv_internal( + prefix: &'static str, + cv: &CommunicationValue, + print_type: Option, +) { + let formatted = format_cv(cv); + + log_internal( + Some(cv.get_sender() as i64), + print_type.unwrap_or(PrintType::General), + prefix, + false, + formatted, + ); +} + +pub fn format_cv(cv: &CommunicationValue) -> String { + let mut parts = Vec::new(); + + let sender = cv.get_sender(); + let receiver = cv.get_receiver(); + + if sender > 0 && receiver > 0 { + parts.push(format!("{} > {}", sender, receiver)); + } else if sender > 0 { + parts.push(format!("{}", sender)); + } else if receiver > 0 { + parts.push(format!("> {}", receiver)); + } + + let comm_type = cv.get_type().to_string(); + parts.push(format!("{}", comm_type)); + + let data: &BTreeMap = cv.get_data_container(); + + let formated_data = + format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); + + parts.push(format!("{}", formated_data)); + + parts.join(": ") +} + +fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { + let parts: Vec = data + .into_iter() + .map(|(key, value)| { + let key_str = key.to_string(); + + match value { + DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{}={{ {} }}", key_str, inner_formatted) + } + + DataValue::Array(arr) => { + let arr_formatted = format_array(arr); + format!("{}=[{}]", key_str, arr_formatted) + } + + DataValue::Bool(b) => format!("{}={}", key_str, b), + + DataValue::BoolTrue => format!("{}=true", key_str), + DataValue::BoolFalse => format!("{}=false", key_str), + + DataValue::Number(num) => format!("{}={}", key_str, num), + + _ => "".to_string(), + } + }) + .collect(); + + parts.join(", ") +} + +fn format_array(arr: Vec) -> String { + let parts: Vec = arr + .into_iter() + .map(|value| match value { + DataValue::Str(s) => format!("\"{}\"", s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{{ {} }}", inner_formatted) + } + + DataValue::Array(inner_arr) => { + let formatted = format_array(inner_arr); + format!("[{}]", formatted) + } + + DataValue::Bool(b) => b.to_string(), + + DataValue::BoolTrue => "true".to_string(), + DataValue::BoolFalse => "false".to_string(), + + DataValue::Number(num) => num.to_string(), + + _ => String::new(), + }) + .collect(); + + parts.join(", ") +} +#[macro_export] +macro_rules! log_cv { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, None) + }; +} + +#[macro_export] +macro_rules! log_cv_in { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, None) + }; +} +#[macro_export] +macro_rules! log_cv_out { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, None) + }; +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..1897223 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,4 @@ +pub mod crypto_helper; +pub mod crypto_util; +pub mod file_util; +pub mod logger; From b9b1f1fc1c7bb25977084800178576d0c5f0bf61 Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 2 Apr 2026 22:28:41 +0200 Subject: [PATCH 104/220] Sync --- .gitignore | 29 + Cargo.lock | 4547 +++++++++++++++++++++++++++ Cargo.toml | 39 + LICENSE | 16 + README.md | 1 + src/main.rs | 65 + src/server/api.rs | 275 ++ src/server/mod.rs | 3 + src/server/server.rs | 67 + src/server/short_link.rs | 87 + src/sql/connection_status.rs | 30 + src/sql/mod.rs | 3 + src/sql/sql.rs | 765 +++++ src/sql/user_online_tracker.rs | 144 + src/transport/mod.rs | 2 + src/transport/omikron_connection.rs | 1139 +++++++ src/transport/omikron_manager.rs | 39 + src/util/crypto_helper.rs | 137 + src/util/crypto_util.rs | 201 ++ src/util/file_util.rs | 289 ++ src/util/logger.rs | 352 +++ src/util/mod.rs | 4 + 22 files changed, 8234 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100755 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 src/main.rs create mode 100644 src/server/api.rs create mode 100644 src/server/mod.rs create mode 100644 src/server/server.rs create mode 100644 src/server/short_link.rs create mode 100644 src/sql/connection_status.rs create mode 100644 src/sql/mod.rs create mode 100644 src/sql/sql.rs create mode 100644 src/sql/user_online_tracker.rs create mode 100644 src/transport/mod.rs create mode 100644 src/transport/omikron_connection.rs create mode 100644 src/transport/omikron_manager.rs create mode 100644 src/util/crypto_helper.rs create mode 100644 src/util/crypto_util.rs create mode 100644 src/util/file_util.rs create mode 100644 src/util/logger.rs create mode 100644 src/util/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3d273c --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# 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/ + + +.env +/logs + +# Added by cargo + +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1444c40 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,4547 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Omega" +version = "0.1.0" +dependencies = [ + "actix-web", + "aes-gcm", + "ansi_term", + "anyhow", + "base64", + "dashmap", + "dotenv", + "epsilon-core", + "epsilon-native", + "hex", + "hkdf", + "json", + "once_cell", + "rand 0.8.5", + "rand_core 0.6.4", + "reqwest", + "rustls", + "rustls-pemfile", + "sha2", + "sqlx", + "strum 0.27.2", + "strum_macros 0.27.2", + "thiserror 2.0.18", + "tokio", + "uuid", + "x448", + "zip", +] + +[[package]] +name = "actix-codec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-core", + "futures-sink", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-http" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-tls", + "actix-utils", + "base64", + "bitflags 2.11.0", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "foldhash", + "futures-core", + "h2 0.3.27", + "http 0.2.12", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand 0.9.2", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "actix-router" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" +dependencies = [ + "bytestring", + "cfg-if", + "http 0.2.12", + "regex", + "regex-lite", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "actix-tls" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "impl-more", + "pin-project-lite", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tokio-util", + "tracing", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-tls", + "actix-utils", + "actix-web-codegen", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "foldhash", + "futures-core", + "futures-util", + "impl-more", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "regex-lite", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2 0.6.3", + "time", + "tracing", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand 2.3.0", + "futures-lite 2.6.1", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "blocking", + "futures-lite 2.6.1", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock 2.8.0", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite 1.13.0", + "log", + "parking", + "polling 2.8.0", + "rustix 0.37.28", + "slab", + "socket2 0.4.10", + "waker-fn", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite 2.6.1", + "parking", + "polling 3.11.0", + "rustix 1.1.4", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" +dependencies = [ + "event-listener 2.5.3", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io 2.6.0", + "async-lock 3.4.2", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite 2.6.1", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[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.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[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 = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite 2.6.1", + "piper", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "bytestring" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +dependencies = [ + "bytes", +] + +[[package]] +name = "bzip2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" +dependencies = [ + "libbz2-rs-sys", +] + +[[package]] +name = "cc" +version = "1.2.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[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" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +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 = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +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 = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[package]] +name = "deflate64" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[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 = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[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" +dependencies = [ + "serde", +] + +[[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 = "epsilon-core" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" +dependencies = [ + "base64", + "byteorder", + "rand 0.8.5", + "strum 0.28.0", + "strum_macros 0.28.0", +] + +[[package]] +name = "epsilon-native" +version = "0.1.0" +source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" +dependencies = [ + "epsilon-core", + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "wtransport", +] + +[[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.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[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-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand 1.9.0", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand 2.3.0", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.0", + "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" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "httlib-huffman" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[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 1.4.0", + "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.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body", + "httparse", + "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 1.4.0", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "system-configuration", + "tokio", + "tower-service", + "tracing", + "windows-registry", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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 = "impl-more" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +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 = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libbz2-rs-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" + +[[package]] +name = "libc" +version = "0.2.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +dependencies = [ + "bitflags 2.11.0", + "libc", + "plain", + "redox_syscall 0.7.3", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "local-channel" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" +dependencies = [ + "futures-core", + "futures-sink", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rust2" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +dependencies = [ + "crc", + "sha2", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[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", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[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 = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "octets" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand 2.3.0", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[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 = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags 1.3.2", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppmd-rust" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" + +[[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.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[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.5", +] + +[[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.5", +] + +[[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.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "aws-lc-rs", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2 0.4.13", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "mime", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-rustls", + "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.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.37.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" +dependencies = [ + "bitflags 1.3.2", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[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.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[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.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "async-io 1.13.0", + "async-std", + "base64", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener 5.4.1", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "async-std", + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags 2.11.0", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64", + "bitflags 2.11.0", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.5", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror 2.0.18", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror 2.0.18", + "tracing", + "url", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +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 = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "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 = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http 1.4.0", + "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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[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.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[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 = "waker-fn" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" + +[[package]] +name = "walkdir" +version = "2.5.0" +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 = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[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.2", +] + +[[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-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[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_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[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_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wtransport" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" +dependencies = [ + "bytes", + "pem", + "quinn", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "sha2", + "socket2 0.5.10", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "url", + "wtransport-proto", + "x509-parser 0.17.0", +] + +[[package]] +name = "wtransport-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" +dependencies = [ + "httlib-huffman", + "octets", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "x448" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" +dependencies = [ + "ed448-goldilocks", + "hex", + "rand_core 0.5.1", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "aws-lc-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +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" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +dependencies = [ + "aes", + "arbitrary", + "bzip2", + "constant_time_eq", + "crc32fast", + "deflate64", + "flate2", + "getrandom 0.3.4", + "hmac", + "indexmap", + "lzma-rust2", + "memchr", + "pbkdf2", + "ppmd-rust", + "sha1", + "time", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100755 index 0000000..34673fb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "Omega" +version = "0.1.0" +edition = "2024" + +[dependencies] +epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } +epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } + +actix-web = { version = "4.12.1", features = ["rustls-0_23"] } +aes-gcm = "*" +ansi_term = "0.12.1" +anyhow = "1.0.101" +base64 = "0.22.1" +dashmap = "6.1.0" +dotenv = "0.15.0" +hex = "0.4.3" +hkdf = "0.12.4" +json = "0.12.4" +once_cell = "1.21.3" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +reqwest = { version = "0.13.2" } +rustls = { version = "0.23.37", default-features = false, features = [ + "std", + "tls12", + "aws-lc-rs", + "prefer-post-quantum", +] } +rustls-pemfile = "2.2.0" +sha2 = "0.10.9" +sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } +strum = "0.27.2" +strum_macros = "0.27.2" +tokio = { version = "*", features = ["full"] } +uuid = { version = "1.19.0", features = ["v4"] } +x448 = "0.6.0" +zip = "6.0.0" +thiserror = "2.0.18" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d218eff --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) [2025] [Methanium] +All rights reserved. + +This software is protected by copyright. Copying, editing, +distributing, publicly performing, or any other use of this software +or its components, in source or binary form, is strictly prohibited without the express +written permission of the copyright holder. + +FUTURE LICENSE ACCEPTANCE: +It is the copyright holder's intention to release this software in the future +under a license yet to be defined, which will, among other things, +allow private, non-commercial use. This statement does not constitute +a current license grant and does not alter the above +prohibition on use, copying, or modification. Until the formal +publication of such a future license, all rights remain +reserved. diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e210a3 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omega) ⚠️ diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..8143c9f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,65 @@ +mod server; +mod sql; +mod transport; +mod util; + +use crate::sql::sql::initialize_db; +use crate::sql::sql::print_users; +use crate::transport::omikron_connection; +use crate::util::crypto_helper::load_public_key; +use crate::util::crypto_helper::load_secret_key; +use crate::util::logger::PrintType; +use crate::util::logger::startup; +use dotenv::dotenv; +use once_cell::sync::Lazy; +use rustls::crypto::aws_lc_rs::default_provider; +use std::env; + +static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); +pub fn get_private_key() -> x448::Secret { + load_secret_key(&*PRIVATE_KEY).unwrap() +} +static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); +pub fn get_public_key() -> x448::PublicKey { + load_public_key(&*PUBLIC_KEY).unwrap() +} + +#[tokio::main] +async fn main() { + if let Err(_) = default_provider().install_default() { + println!("Error loading Provider"); + return; + } + dotenv().ok(); + startup(); + log_in!("Incoming messages"); + log_out!("Outgoing messages"); + + tokio::spawn(async move { + match omikron_connection::start(9187).await { + Err(e) => log_err!(0, PrintType::General, "{:?}", e), + _ => {} + } + }); + + log!("Started"); + log!(" .env"); + if let Err(e) = initialize_db().await { + log!("[FATAL] Database initialization failed: {}", e); + log!( + "[FATAL] Please ensure the database is running and the .env file is configured correctly." + ); + return; + } else { + log!(" DB"); + } + if let Err(e) = print_users().await { + log!("[ERROR] Failed to print users: {}", e); + } else { + log!(" Users"); + } + + let _ = server::server::start(9188).await; + + tokio::signal::ctrl_c().await.unwrap(); +} diff --git a/src/server/api.rs b/src/server/api.rs new file mode 100644 index 0000000..eb6f9cf --- /dev/null +++ b/src/server/api.rs @@ -0,0 +1,275 @@ +use crate::get_public_key; +use crate::sql::sql; +use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; +use crate::transport::omikron_manager::get_random_omikron; +use crate::util::file_util::get_directory; +use crate::{ + sql::sql::{get_by_user_id, get_omikron_by_id}, + util::crypto_helper::public_key_to_base64, +}; +use actix_web::HttpResponse; +use actix_web::http::{StatusCode, header}; +use base64::Engine as _; +use json::JsonValue; + +pub async fn handle(path: &str, body_string: Option) -> HttpResponse { + if path == "OPTIONS" { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) + .insert_header(("Access-Control-Allow-Headers", "*")) + .finish(); + } + + let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); + + let _body: Option = if body_string.is_some() { + if let Ok(body_json) = json::parse(&body_string.unwrap()) { + Some(body_json) + } else { + None + } + } else { + None + }; + + let (status, body_text) = match path_parts.as_slice() { + // ================================================== + // DOWNLOAD IOTA FRONTEND + // ================================================== + ["api", "download", "iota_frontend"] => { + let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); + + match std::fs::read(file_path) { + Ok(file_bytes) => { + return HttpResponse::Ok() + .insert_header(("Access-Control-Allow-Origin", "*")) + .insert_header(("Content-Type", "application/zip")) + .insert_header(( + "Content-Disposition", + "attachment; filename=\"iota_frontend.zip\"", + )) + .body(file_bytes); + } + Err(_) => { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + return HttpResponse::NotFound() + .insert_header(("Access-Control-Allow-Origin", "*")) + .body(res.dump()); + } + } + } + + // ================================================== + // GET RANDOM OMIKRON + // ================================================== + ["api", "get", "omikron"] => { + if let Ok(omikron_conn) = get_random_omikron().await { + if let Some(id) = omikron_conn.get_omikron_id().await { + if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + + // ================================================== + // GET OMIKRON BY ID + // ================================================== + ["api", "get", "omikron", id] => { + let id = id.parse::().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await + { + if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { + if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["id"] = omikron_id.into(); + res["public_key"] = public_key.into(); + res["ip_address"] = ip_address.into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + + // ================================================== + // GET ID BY USERNAME + // ================================================== + ["api", "get", "id", username] => { + if username.is_empty() { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok(( + id, + iota_id, + username, + _, + _, + _, + _, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_username(username).await + { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) + } + } + + // ================================================== + // GET SERVER PUBLIC KEY + // ================================================== + ["api", "get", "public_key"] => { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["public_key"] = public_key_to_base64(&get_public_key()).into(); + (StatusCode::OK, res.dump()) + } + + // ================================================== + // GET USER BY ID + // ================================================== + ["api", "get", "user", id] => { + let id: i64 = id.parse().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok(( + id, + iota_id, + username, + display, + status_msg, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + )) = sql::get_by_user_id(id).await + { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["username"] = username.into(); + res["public_key"] = public_key.into(); + res["user_id"] = id.into(); + res["iota_id"] = iota_id.into(); + res["sub_level"] = sub_level.into(); + res["sub_end"] = sub_end.into(); + + if let Some(display) = display { + res["display"] = display.into(); + } + if let Some(status_msg) = status_msg { + res["status_message"] = status_msg.into(); + } + if let Some(about) = about { + res["about"] = about.into(); + } + if let Some(avatar) = avatar { + res["avatar"] = base64::engine::general_purpose::STANDARD + .encode(avatar) + .into(); + } + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::OK, res.dump()) + } + } + + // ================================================== + // DEFAULT + // ================================================== + _ => { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + }; + let body_bytes = body_text.into_bytes(); + + HttpResponse::build(status) + .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) + .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) + .body(body_bytes) +} diff --git a/src/server/mod.rs b/src/server/mod.rs new file mode 100644 index 0000000..8645e6e --- /dev/null +++ b/src/server/mod.rs @@ -0,0 +1,3 @@ +pub mod api; +pub mod server; +pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs new file mode 100644 index 0000000..3618eb7 --- /dev/null +++ b/src/server/server.rs @@ -0,0 +1,67 @@ +use crate::{ + log, + server::{api, short_link::get_short_link}, + util::file_util::load_file_buf, +}; + +use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; + +use rustls::ServerConfig; +use rustls::pki_types::{CertificateDer, PrivateKeyDer}; +use rustls_pemfile::{certs, pkcs8_private_keys}; + +pub async fn start(port: u16) -> anyhow::Result<()> { + let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; + + let mut key_reader = load_file_buf("certs", "server_key.pem")?; + + let cert_chain: Vec> = + certs(&mut cert_reader).collect::>()?; + + let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) + .map(|res| res.map(Into::into)) + .collect::>()?; + + let key = keys.remove(0); + + let mut config = ServerConfig::builder() + .with_no_client_auth() + .with_single_cert(cert_chain, key)?; + + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + + let addr = format!("0.0.0.0:{port}"); + log!(" Server on {}", addr); + + HttpServer::new(move || { + App::new() + .route("/api/{path:.*}", web::to(api_handler)) + .route("/direct/{path:.*}", web::to(direct_handler)) + }) + .bind_rustls_0_23(addr, config)? + .run() + .await?; + + Ok(()) +} +async fn direct_handler(req: HttpRequest) -> impl Responder { + let path = req.uri().path().to_string(); + let short = path.replace("/direct/", ""); + + if let Ok(long) = get_short_link(&short).await { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, long)) + .finish() + } else { + HttpResponse::TemporaryRedirect() + .append_header((header::LOCATION, "https://tensamin.net")) + .finish() + } +} + +async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { + let path = req.uri().path().to_string(); + let body_string = String::from_utf8_lossy(&body).to_string(); + + api::handle(&path, Some(body_string)).await +} diff --git a/src/server/short_link.rs b/src/server/short_link.rs new file mode 100644 index 0000000..c0ab51d --- /dev/null +++ b/src/server/short_link.rs @@ -0,0 +1,87 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::{Rng, thread_rng}; + +static LINKS: Lazy> = Lazy::new(DashMap::new); + +const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; + +pub async fn add_short_link(long: &str) -> Result { + let raw = generate_unique_short_link().await; + LINKS.insert(raw.clone(), long.to_string()); + + Ok(format!( + "https://omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )) +} + +async fn generate_unique_short_link() -> String { + loop { + let short = generate_short_link().await; + if !LINKS.contains_key(&short) { + return short; + } + } +} + +pub async fn generate_short_link() -> String { + let len = short_length(); + + let mut rng = thread_rng(); + (0..len) + .map(|_| { + let idx = rng.gen_range(0..CHARSET.len()); + CHARSET[idx] as char + }) + .collect() +} + +pub async fn get_short_link(short: &str) -> Result { + let key = if short.contains("/") { + short.split("/").nth(1).unwrap_or_default() + } else { + short + }; + let frag = short.replace(key, ""); + let normalized = normalize_short(&key); + + if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { + Ok(format!("{}{}", t, frag)) + } else { + Err(()) + } +} + +/* ---------------- helpers ---------------- */ + +fn short_length() -> usize { + let count = LINKS.len(); + + match count { + 0..=1_999 => 4, + 2_000..=999_999 => 8, + _ => 12, + } +} + +fn format_with_dashes(s: &str) -> String { + s.chars() + .collect::>() + .chunks(4) + .map(|c| c.iter().collect::()) + .collect::>() + .join("-") +} + +fn normalize_short(input: &str) -> String { + input + .chars() + .filter(|c| *c != '-') + .map(|c| match c { + 'Q' | 'O' => '0', + 'I' => 'l', + _ => c, + }) + .collect() +} diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs new file mode 100644 index 0000000..a175e6d --- /dev/null +++ b/src/sql/connection_status.rs @@ -0,0 +1,30 @@ +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] +#[allow(unused, non_camel_case_types)] +pub enum UserStatus { + user_offline, + user_online, + user_dnd, + user_idle, + user_wc, + user_borked, + iota_offline, + iota_online, + iota_borked, +} +#[allow(unused)] +impl UserStatus { + pub fn to_string(&self) -> String { + format!("{:?}", self) + } + pub fn from_str(s: &str) -> Option { + for sel in UserStatus::iter() { + if &sel.to_string() == s { + return Some(sel); + } + } + None + } +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs new file mode 100644 index 0000000..91ba841 --- /dev/null +++ b/src/sql/mod.rs @@ -0,0 +1,3 @@ +pub mod connection_status; +pub mod sql; +pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs new file mode 100644 index 0000000..8445e26 --- /dev/null +++ b/src/sql/sql.rs @@ -0,0 +1,765 @@ +use crate::log; +use once_cell::sync::Lazy; +use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; +use std::sync::atomic::{AtomicU64, Ordering}; +use std::{ + env, + sync::Arc, + time::{SystemTime, UNIX_EPOCH}, +}; +use tokio::sync::RwLock; + +/* +use crate::sql::{ + iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, + sql::{ + change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, + change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, + get_register_id, register_complete_iota, register_complete_user, + }, +}; + */ + +static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); + +pub async fn connect() -> Result, sqlx::Error> { + let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); + let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); + let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); + + MySqlPoolOptions::new() + .max_connections(200) + .connect(&format!( + "mysql://{}:{}@127.0.0.1:3306/{}", + user, passwd, table + )) + .await +} +// Omega +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +// - Omikron +// - Iota +// - User +// - User +// - Iota +// - User +// - User +pub async fn initialize_db() -> Result<(), sqlx::Error> { + let pool = connect().await?; + let mut db_lock = SQL_DB.write().await; + // create tables + // with indexes + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + users ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, + display VARCHAR(15) COLLATE utf8mb4_bin, + status VARCHAR(15) COLLATE utf8mb4_bin, + about VARCHAR(200) COLLATE utf8mb4_bin, + avatar MEDIUMBLOB, + sub_level INT(11) NOT NULL DEFAULT 0, + sub_end BIGINT(20) NOT NULL DEFAULT 0, + public_key TEXT NOT NULL COLLATE utf8mb4_bin, + private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', + iota_id BIGINT UNSIGNED NOT NULL, + token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + iotas ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + omikrons ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + )", + ) + .execute(&pool) + .await; + let _ = sqlx::query( + "CREATE TABLE IF NOT EXISTS + notifications ( + id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, + sender_id BIGINT UNSIGNED NOT NULL, + receiver_id BIGINT UNSIGNED NOT NULL, + amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + )", + ) + .execute(&pool) + .await; + *db_lock = Some(pool); + Ok(()) +} + +// ========================================================================================== +// REGISTER +// ========================================================================================== +pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); +pub static CURRENT_REGISTER_PROCESS: Lazy>>> = + Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); + +pub async fn get_register_id() -> u64 { + let mut current_time = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; + + loop { + let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); + + if current_locked < current_time { + let result = CURRENT_MILLI_USED.compare_exchange( + current_locked, // expected value + current_time, // new value + Ordering::SeqCst, // acquire/release ordering + Ordering::SeqCst, // failure ordering + ); + + match result { + Ok(_) => { + CURRENT_REGISTER_PROCESS.write().await.push(current_time); + return current_time; + } + Err(_) => { + continue; + } + } + } else { + current_time = current_locked + 1; + } + } +} + +// ========================================================================================== +// USERS +// ========================================================================================== + +pub async fn get_by_username( + username: &str, +) -> Result< + ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", + ) + .bind(username) + .fetch_optional(&pool) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + _ => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_by_user_id( + id: i64, +) -> Result< + ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(&pool) + .await?; + + match row { + Some(row) => { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + Ok(( + id, + iota_id, + username, + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )) + } + _ => Err(sqlx::Error::RowNotFound), + } +} + +pub async fn get_users_by_iota_id( + iota_id_param: i64, +) -> Result< + Vec<( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + )>, + sqlx::Error, +> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let rows = sqlx::query( + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", + ) + .bind(iota_id_param) + .fetch_all(&pool) + .await?; + + let mut users = Vec::new(); + for row in rows { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let avatar: Option> = row.get("avatar"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + let public_key: String = row.get("public_key"); + let private_key_hash: String = row.get("private_key_hash"); + let token: Vec = row.get("token"); + + users.push(( + id, + iota_id, + username, + display.map(|d| String::from_utf8_lossy(&d).to_string()), + status.map(|s| String::from_utf8_lossy(&s).to_string()), + about.map(|a| String::from_utf8_lossy(&a).to_string()), + avatar, + sub_level, + sub_end, + public_key, + private_key_hash, + String::from_utf8_lossy(&token).to_string(), + )); + } + + Ok(users) +} + +pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_username) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_display) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_avatar) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_about) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_status) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") + .bind(new_iota_id) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn change_keys( + id: i64, + new_public_key: String, + new_private_key_hash: String, +) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query( + "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_token) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn register_complete_user( + id: i64, + username: String, + public_key: String, + iota_id: i64, + token: String, +) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id) + .bind(username) + .bind(public_key) + .bind(iota_id) + .bind(token) + .execute(&pool) + .await?; + + Ok(()) +} +pub async fn print_users() -> Result<(), Box> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + log!("Printing users..."); + for row in sqlx::query( + "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", + ) + .fetch_all(&pool) + .await? + .iter() + { + let id: i64 = row.get("id"); + let iota_id: i64 = row.get("iota_id"); + let username: String = row.get("username"); + let display: Option> = row.get("display"); + let status: Option> = row.get("status"); + let about: Option> = row.get("about"); + let sub_level: i32 = row.get("sub_level"); + let sub_end: i64 = row.get("sub_end"); + + log!( + "User: {:?}", + ( + id, + iota_id, + username, + display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), + status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), + about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), + sub_level, + sub_end + ) + ); + } + + Ok(()) +} + +// ========================================================================================== +// IOTA +// ========================================================================================== +pub async fn create_new_iota(public_key: String) -> Result { + let new_id = get_register_id().await as i64; + register_complete_iota(new_id, public_key).await?; + Ok(new_id) +} + +pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") + .bind(id) + .bind(public_key) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let result = sqlx::query_as::<_, (u64, Vec)>( + "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(&pool) + .await; + + match result { + Ok(optional_row) => match optional_row { + Some((id_u64, public_key)) => Ok(( + id_u64 as i64, + String::from_utf8_lossy(&public_key).to_string(), + )), + _ => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } +} + +pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") + .bind(new_key) + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") + .bind(id) + .execute(&pool) + .await?; + + Ok(()) +} + +// ========================================================================================== +// OMIKRONS +// ========================================================================================== + +pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { + let pool = { + let db_lock = SQL_DB.read().await; + db_lock + .as_ref() + .cloned() + .expect("Database pool not initialized") + }; + + let row = sqlx::query_as::<_, (Vec, Vec)>( + "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", + ) + .bind(id) + .fetch_optional(&pool) + .await?; + + match row { + Some((public_key, ip_address)) => Ok(( + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&ip_address).to_string(), + )), + _ => Err(sqlx::Error::RowNotFound), + } +} + +// ========================================================================================== +// PHI +// ========================================================================================== + +pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + INSERT INTO notifications (sender_id, receiver_id, amount) + VALUES (?, ?, 1) + ON DUPLICATE KEY UPDATE amount = amount + 1 + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query( + r#" + DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? + "#, + ) + .bind(sender_id) + .bind(receiver_id) + .execute(pool) + .await?; + + Ok(()) +} +pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { + let db_lock = SQL_DB.read().await; + let pool = db_lock.as_ref().expect("Database pool is not initialized"); + + sqlx::query_as::<_, (i64, i64)>( + r#" + SELECT sender_id, amount FROM notifications WHERE receiver_id = ? + "#, + ) + .bind(user_id) + .fetch_all(pool) + .await +} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs new file mode 100644 index 0000000..42dfa22 --- /dev/null +++ b/src/sql/user_online_tracker.rs @@ -0,0 +1,144 @@ +use crate::sql; +use crate::sql::connection_status::UserStatus; +use dashmap::DashMap; +use once_cell::sync::Lazy; + +#[derive(Debug, Clone)] +pub struct UserConnection { + pub connection_type: UserStatus, + pub omikron_id: i64, +} + +// IotaID -> Primary OmikronID +static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); + +// IotaID -> Vec +static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); + +// UserID -> UserStatus +static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); + +pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { + let mut entry = IOTA_OMIKRON_CONNECTIONS + .entry(iota_id) + .or_insert_with(Vec::new); + + if !entry.contains(&omikron_id) { + entry.push(omikron_id); + } + + if primary { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); + } +} + +pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { + let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { + let mut vec = r.value().clone(); + vec.retain(|&id| id != omikron_id); + let empty = vec.is_empty(); + drop(r); + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); + empty + } else { + false + }; + + if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { + let primary_id = *primary_ref.value(); + drop(primary_ref); + if primary_id == omikron_id { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + } + + if connections_empty { + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + } + + connections_empty +} + +pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { + IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) +} + +pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { + IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) +} + +pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { + USER_STATUS_MAP.insert( + user_id, + UserConnection { + connection_type: status, + omikron_id, + }, + ); +} + +pub fn get_user_status(user_id: i64) -> Option { + USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) +} + +pub fn untrack_many_users(user_ids: &[i64]) { + for user_id in user_ids { + USER_STATUS_MAP.remove(user_id); + } +} + +pub async fn untrack_omikron(omikron_id: i64) { + let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION + .iter() + .filter(|entry| *entry.value() == omikron_id) + .map(|entry| *entry.key()) + .collect(); + + for key in primary_keys_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); + } + + let mut offline_iotas = Vec::new(); + let mut primary_to_remove = Vec::new(); + + // Collect iotas and primary info first + for r in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *r.key(); + let mut connections = r.value().clone(); + connections.retain(|&id| id != omikron_id); + + if connections.is_empty() { + offline_iotas.push(iota_id); + } + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(&iota_id) + .map(|p| *p == omikron_id) + .unwrap_or(false) + { + primary_to_remove.push(iota_id); + } + + // Update the connections vector after filtering + IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); + } + + // Step 2: Remove primary connections safely + for iota_id in primary_to_remove { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + + // Step 3: Remove users that were on this omikron + USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); + + // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP + for iota_id in offline_iotas { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { + for user in users { + USER_STATUS_MAP.remove(&user.0); + } + } + // Finally remove the empty connections vector + IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + } +} diff --git a/src/transport/mod.rs b/src/transport/mod.rs new file mode 100644 index 0000000..4f3953a --- /dev/null +++ b/src/transport/mod.rs @@ -0,0 +1,2 @@ +pub mod omikron_connection; +pub mod omikron_manager; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs new file mode 100644 index 0000000..504d723 --- /dev/null +++ b/src/transport/omikron_connection.rs @@ -0,0 +1,1139 @@ +use crate::{ + get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, + server::short_link::add_short_link, + sql::{ + connection_status::UserStatus, + sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, + user_online_tracker::{self}, + }, + transport::omikron_manager, + util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use dashmap::DashMap; +use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use epsilon_native::{Host, Receiver, Sender}; +use rand::{Rng, distributions::Alphanumeric}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; +use tokio::{ + sync::{Mutex, RwLock}, + time::interval, +}; +use x448::PublicKey; + +// ============================================================================ +// Configuration +// ============================================================================ + +const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); +const MAX_WAITING_AGE: Duration = Duration::from_secs(60); + +// ============================================================================ +// Error Types +// ============================================================================ + +#[derive(Debug, thiserror::Error)] +pub enum OmikronError { + #[error("Not connected")] + NotConnected, + #[error("Not authenticated")] + NotAuthenticated, + #[error("Invalid response")] + InvalidResponse, + #[error("Authentication failed")] + AuthenticationFailed, + #[error("SQL error: {0}")] + Sql(String), + #[error("Send error: {0}")] + Send(String), +} + +pub type OmikronResult = Result; + +// ============================================================================ +// Waiting Task System (Preserved from original) +// ============================================================================ + +pub struct WaitingTask { + pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub inserted_at: Instant, +} + +// ============================================================================ +// Connection State +// ============================================================================ + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum AuthState { + Unauthenticated, + Identified { omikron_id: i64 }, + Authenticated { omikron_id: i64 }, +} + +impl AuthState { + fn is_authenticated(&self) -> bool { + match self { + AuthState::Authenticated { omikron_id } => true, + _ => false, + } + } + + fn omikron_id(&self) -> Option { + match self { + AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { + Some(*omikron_id) + } + _ => None, + } + } +} + +// ============================================================================ +// Omikron Connection (Epsilon/QUIC-based) +// ============================================================================ + +pub struct OmikronConnection { + id: u64, + sender: Mutex>, + state: RwLock, + challenge: RwLock, + pub_key: RwLock>>, + pub ping: RwLock, + waiting_tasks: DashMap, + cleanup_handle: Mutex>>, +} + +impl OmikronConnection { + // ------------------------------------------------------------------------- + // Construction + // ------------------------------------------------------------------------- + + pub fn new(sender: Sender) -> Arc { + let conn = Arc::new(Self { + id: rand::random(), + sender: Mutex::new(Some(sender)), + state: RwLock::new(AuthState::Unauthenticated), + challenge: RwLock::new(String::new()), + pub_key: RwLock::new(None), + ping: RwLock::new(-1), + waiting_tasks: DashMap::new(), + cleanup_handle: Mutex::new(None), + }); + + conn + } + + // ------------------------------------------------------------------------- + // Main Handler Loop + // ------------------------------------------------------------------------- + + pub async fn handle(self: Arc, receiver: &mut Receiver) { + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection started" + ); + + // Start cleanup task + let cleanup_conn = self.clone(); + let cleanup_handle = tokio::spawn(async move { + let mut ticker = interval(CLEANUP_INTERVAL); + loop { + ticker.tick().await; + cleanup_conn + .waiting_tasks + .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + } + }); + *self.cleanup_handle.lock().await = Some(cleanup_handle); + + while let Ok(cv) = receiver.receive().await { + if let Err(e) = self.clone().process_message(cv).await { + log_err!(0, PrintType::Omega, "Error processing message: {}", e); + if matches!(e, OmikronError::NotConnected) { + break; + } + } + } + + self.clone().cleanup().await; + log_in!( + self.id as i64, + PrintType::Omega, + "Omikron connection closed" + ); + } + + // ------------------------------------------------------------------------- + // Message Processing + // ------------------------------------------------------------------------- + + async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_in!(PrintType::Omikron, &cv); + } + + let msg_id = cv.get_id(); + + // Check waiting tasks first (response to previous request) + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + let _ = (task.task)(self.clone(), cv); + return Ok(()); + } + + // Handle ping regardless of auth state + if cv.is_type(CommunicationType::ping) { + return self.handle_ping(cv).await; + } + + let current_state = *self.state.read().await; + // Route based on authentication state + match current_state { + AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, + AuthState::Identified { .. } => self.clone().handle_identified(cv).await, + AuthState::Authenticated { omikron_id } => { + self.clone().handle_authenticated(cv, omikron_id).await + } + } + } + + // ------------------------------------------------------------------------- + // Authentication Handlers + // ------------------------------------------------------------------------- + + async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::identification) { + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); + } + + // Extract omikron ID + let omikron_id = cv + .get_data(DataTypes::omikron_id) + .as_number() + .ok_or(OmikronError::InvalidResponse)?; + log!("Omikron {:?} connected", omikron_id); + + // Lookup omikron in database + let (public_key, _) = get_omikron_by_id(omikron_id) + .await + .map_err(|e| OmikronError::Sql(e.to_string()))?; + + log!("Got public Key"); + + let pub_key_bytes = STANDARD + .decode(&public_key) + .map_err(|_| OmikronError::AuthenticationFailed)?; + + let pub_key_bytes_clone = pub_key_bytes.clone(); + let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) + .ok_or(OmikronError::AuthenticationFailed)?; + + log!("Decoded public Key"); + + // Generate challenge + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + log!("Generated Challenge"); + + *self.challenge.write().await = challenge.clone(); + + log!("Stored Challenge"); + + *self.pub_key.write().await = Some(pub_key_bytes); + + log!("Stored Pubkey"); + + *self.state.write().await = AuthState::Identified { omikron_id }; + + log!("Stored State"); + + let challenge_clone = challenge.clone(); + let private_key = get_private_key(); + let public_key_for_encrypt = omikron_pub_key; + + let encrypted = tokio::task::spawn_blocking(move || { + encrypt(private_key, public_key_for_encrypt, &challenge_clone) + .map_err(|_| OmikronError::AuthenticationFailed) + }) + .await + .map_err(|_| OmikronError::AuthenticationFailed)??; + + log!("Encrypted Challenge"); + + // Send challenge response + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + + log!("Sending Challenge"); + self.send(&response).await + } + + async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::challenge_response) { + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .await; + return Err(OmikronError::NotAuthenticated); + } + + let client_response = cv + .get_data(DataTypes::challenge) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; + + let expected_challenge = self.challenge.read().await.clone(); + + if client_response == expected_challenge { + let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); + *self.state.write().await = AuthState::Authenticated { omikron_id }; + + omikron_manager::add_omikron(self.clone()).await; + + let response = CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, DataValue::Bool(true)); + + self.clone().send(&response).await?; + log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); + Ok(()) + } else { + let _ = self + .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + .await; + Err(OmikronError::AuthenticationFailed) + } + } + + // ------------------------------------------------------------------------- + // Authenticated Message Handlers + // ------------------------------------------------------------------------- + + async fn handle_authenticated( + self: Arc, + cv: CommunicationValue, + omikron_id: i64, + ) -> OmikronResult<()> { + match cv.get_type() { + // Link shortening + CommunicationType::shorten_link => self.handle_shorten_link(cv).await, + + // Online status tracking + CommunicationType::user_connected => { + self.handle_user_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::user_disconnected => { + self.handle_user_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_connected => { + self.handle_iota_connected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::iota_disconnected => { + self.handle_iota_disconnected(cv, omikron_id).await; + Ok(()) + } + CommunicationType::sync_client_iota_status => { + self.handle_sync_status(cv, omikron_id).await; + Ok(()) + } + + CommunicationType::get_user_data => self.handle_get_user_data(cv).await, + CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, + + CommunicationType::get_register => self.handle_get_register(cv).await, + CommunicationType::complete_register_iota => { + self.handle_complete_register_iota(cv).await + } + CommunicationType::complete_register_user => { + self.handle_complete_register_user(cv).await + } + + CommunicationType::change_user_data => self.handle_change_user_data(cv).await, + CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, + CommunicationType::delete_user => self.handle_delete_user(cv).await, + CommunicationType::delete_iota => self.handle_delete_iota(cv).await, + + CommunicationType::get_notifications => self.handle_get_notifications(cv).await, + CommunicationType::read_notification => self.handle_read_notification(cv).await, + CommunicationType::push_notification => self.handle_push_notification(cv).await, + + _ => { + log_err!( + 0, + PrintType::Omega, + "Unknown message type: {:?}", + cv.get_type() + ); + Ok(()) + } + } + } + + // ------------------------------------------------------------------------- + // Specific Handlers (ported from original WebSocket implementation) + // ------------------------------------------------------------------------- + + async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let link = cv + .get_data(DataTypes::link) + .as_str() + .ok_or(OmikronError::InvalidResponse)?; + + let short = add_short_link(link) + .await + .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; + + let response = CommunicationValue::new(CommunicationType::shorten_link) + .with_id(cv.get_id()) + .add_data(DataTypes::link, DataValue::Str(short)); + + self.send(&response).await + } + + async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "User connected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_online, + omikron_id, + ); + } + } + + async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { + log_in!(PrintType::Omega, "User disconnected"); + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { + user_online_tracker::track_user_status( + user_id as i64, + UserStatus::user_offline, + status.omikron_id, + ); + } + } + } + + async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA connected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + + let mut user_ids = Vec::new(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { + user_ids.push(DataValue::Number(user_id)); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_offline, + omikron_id, + ); + } + } else { + log_in!(PrintType::General, "SQL error loading users for IOTA"); + } + + let response = CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + + let _ = self.send(&response).await; + } else { + log_in!(PrintType::General, "No IOTA ID found"); + } + } + + async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { + log_in!(PrintType::Omega, "IOTA disconnected"); + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = iota_id as i64; + let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); + } + } + } + } + + async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { + if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { + for user_id_val in user_ids { + if let DataValue::Number(user_id) = user_id_val { + user_online_tracker::track_user_status( + *user_id, + UserStatus::user_online, + omikron_id, + ); + } + } + } + + if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { + for iota_id_val in iota_ids { + if let DataValue::Number(iota_id) = iota_id_val { + user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); + } + } + } + } + + async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + // Try by user_id first + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok(user_data) = get_by_user_id(user_id as i64).await { + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; + return self.send(&response).await; + } + } + + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok(user_data) = get_by_username(username).await { + let response = self + .clone() + .build_user_data_response(cv.get_id(), user_data) + .await; + return self.send(&response).await; + } + } + + // Not found + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_user_data_response( + self: Arc, + msg_id: u32, + user: ( + i64, + i64, + String, + Option, + Option, + Option, + Option>, + i32, + i64, + String, + String, + String, + ), + ) -> CommunicationValue { + let ( + id, + iota_id, + username, + display, + status, + about, + avatar, + sub_level, + sub_end, + public_key, + _, + _, + ) = user; + + let mut response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(msg_id) + .add_data(DataTypes::username, DataValue::Str(username.clone())) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::user_id, DataValue::Number(id)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) + .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) + .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); + + // Display name (fallback to username) + let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); + response = response.add_data(DataTypes::display, DataValue::Str(display_name)); + + // Optional fields + if let Some(s) = status.filter(|s| !s.is_empty()) { + response = response.add_data(DataTypes::status, DataValue::Str(s)); + } + if let Some(a) = about.filter(|a| !a.is_empty()) { + response = response.add_data(DataTypes::about, DataValue::Str(a)); + } + if let Some(av) = avatar { + response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); + } + + // Online status + let user_status = user_online_tracker::get_user_status(id); + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + if let Some(us) = user_status { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(us.connection_type.to_string()), + ); + response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); + } else { + response = response.add_data( + DataTypes::online_status, + DataValue::Str(UserStatus::iota_offline.to_string()), + ); + } + + response = response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ); + + response + } + + async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + // Try by iota_id + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .clone() + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; + } + } + + // Try by user_id + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_user_id(user_id as i64).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .clone() + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id as i64), + None, + ) + .await; + return self.send(&response).await; + } + } + } + + // Try by username + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = + get_by_username(username).await + { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { + let response = self + .clone() + .build_iota_data_response( + cv.get_id(), + iota_id, + public_key, + Some(user_id), + Some(username.to_string()), + ) + .await; + return self.send(&response).await; + } + } + } + + let response = + CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + self.send(&response).await + } + + async fn build_iota_data_response( + self: Arc, + msg_id: u32, + iota_id: i64, + public_key: String, + user_id: Option, + username: Option, + ) -> CommunicationValue { + let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + .with_id(msg_id) + .add_data(DataTypes::public_key, DataValue::Str(public_key)) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + + if let Some(uid) = user_id { + response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); + } + if let Some(uname) = username { + response = response.add_data(DataTypes::username, DataValue::Str(uname)); + } + + let iota_connections = + user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); + + response.add_data( + DataTypes::omikron_connections, + DataValue::Array( + iota_connections + .into_iter() + .map(DataValue::Number) + .collect(), + ), + ) + } + + async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let register_id = sql::get_register_id().await; + let response = CommunicationValue::new(CommunicationType::get_register) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); + self.send(&response).await + } + + async fn handle_complete_register_iota( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let iota_id_opt = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + + if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { + if let Some(iota_id) = iota_id_opt { + // Register existing IOTA + match sql::register_complete_iota(iota_id, public_key.to_string()).await { + Ok(_) => { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + // Create new IOTA + match sql::create_new_iota(public_key.to_string()).await { + Ok(new_iota_id) => { + let response = + CommunicationValue::new(CommunicationType::complete_register_iota) + .with_id(cv.get_id()) + .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_complete_register_user( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv + .get_data(DataTypes::user_id) + .as_number() + .map(|n| n as i64); + let username = cv + .get_data(DataTypes::username) + .as_str() + .map(|s| s.to_string()); + let public_key = cv + .get_data(DataTypes::public_key) + .as_str() + .map(|s| s.to_string()); + let iota_id = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64); + let reset_token = cv + .get_data(DataTypes::reset_token) + .as_str() + .map(|s| s.to_string()); + + if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = + (user_id, username, public_key, iota_id, reset_token) + { + match sql::register_complete_user(uid, uname, pk, iid, rt).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + let mut success = true; + let mut error_message = String::new(); + + // Process each field + if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Err(e) = sql::change_username(user_id, username.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(display) = cv.get_data(DataTypes::display).as_str() { + if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { + if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(about) = cv.get_data(DataTypes::about).as_str() { + if let Err(e) = sql::change_about(user_id, about.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let Some(status) = cv.get_data(DataTypes::status).as_str() { + if let Err(e) = sql::change_status(user_id, status.to_string()).await { + success = false; + error_message = e.to_string(); + } + } + if let (Some(public_key), Some(private_key_hash)) = ( + cv.get_data(DataTypes::public_key).as_str(), + cv.get_data(DataTypes::private_key_hash).as_str(), + ) { + if let Err(e) = sql::change_keys( + user_id, + public_key.to_string(), + private_key_hash.to_string(), + ) + .await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } + + async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + + if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( + cv.get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64), + cv.get_data(DataTypes::reset_token).as_str(), + cv.get_data(DataTypes::new_token).as_str(), + ) { + match sql::get_by_user_id(user_id).await { + Ok(user) => { + let current_token = user.11; // reset_token field + if current_token == reset_token { + let mut success = true; + let mut error_message = String::new(); + + if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + success = false; + error_message = e.to_string(); + } + if success { + if let Err(e) = sql::change_token(user_id, new_token.to_string()).await + { + success = false; + error_message = e.to_string(); + } + } + + if success { + let response = CommunicationValue::new(CommunicationType::success) + .with_id(cv.get_id()); + self.send(&response).await + } else { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); + self.send(&response).await + } + } else { + self.send_error_response( + cv.get_id(), + CommunicationType::error_invalid_challenge, + ) + .await + } + } + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error_not_found) + .await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + match sql::delete_user(user_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } + + async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if let Some(iota_id) = cv + .get_data(DataTypes::iota_id) + .as_number() + .map(|n| n as i64) + { + match sql::delete_iota(iota_id).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await + } + } + } else { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + .await + } + } + + async fn handle_get_notifications( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Ok(notifications) = sql::get_notifications(user_id).await { + let json_array: Vec = notifications + .into_iter() + .map(|(sender, amount)| { + DataValue::Container(vec![ + (DataTypes::sender_id, DataValue::Number(sender)), + (DataTypes::amount, DataValue::Number(amount)), + ]) + }) + .collect(); + + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_data(DataTypes::notifications, DataValue::Array(json_array)); + self.send(&response).await + } else { + Ok(()) + } + } + + async fn handle_read_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::read_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::read_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_push_notification( + self: Arc, + cv: CommunicationValue, + ) -> OmikronResult<()> { + let user_id = cv.get_sender() as i64; + if let Some(other_id) = cv + .get_data(DataTypes::sender_id) + .as_number() + .map(|n| n as i64) + { + if sql::add_notification(user_id, other_id).await.is_ok() { + let response = CommunicationValue::new(CommunicationType::push_notification) + .with_id(cv.get_id()); + self.send(&response).await + } else { + Ok(()) + } + } else { + Ok(()) + } + } + + async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + *self.ping.write().await = *last_ping; + } + + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + self.send(&response).await + } + + // ------------------------------------------------------------------------- + // Utilities + // ------------------------------------------------------------------------- + + async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Omikron, cv); + } + + let guard = self.sender.lock().await; + let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; + + sender + .send(cv) + .await + .map_err(|e| OmikronError::Send(e.to_string())) + } + + async fn send_error_response( + self: Arc, + message_id: u32, + error_type: CommunicationType, + ) -> OmikronResult<()> { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send(&error).await + } + + pub async fn close(self: Arc) { + log_in!( + self.get_omikron_id().await.unwrap_or(0), + PrintType::Omega, + "Omikron connection Closed" + ); + } + + async fn cleanup(self: Arc) { + if let Some(omikron_id) = self.state.read().await.omikron_id() { + if omikron_id != 0 { + log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); + omikron_manager::remove_omikron(omikron_id).await; + user_online_tracker::untrack_omikron(omikron_id).await; + } + } + + // Cancel cleanup task + if let Some(handle) = self.cleanup_handle.lock().await.take() { + handle.abort(); + } + } + + // Public API for external use + pub async fn is_authenticated(self: Arc) -> bool { + self.state.read().await.is_authenticated() + } + + pub async fn get_omikron_id(self: Arc) -> Option { + self.state.read().await.omikron_id() + } + + pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { + self.send(cv).await + } +} + +// ============================================================================ +// Server Startup +// ============================================================================ + +pub async fn start(port: u16) -> Result<(), Box> { + let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); + + let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); + + let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; + log!("OmikronServer listening on port {}", port); + + while let Some((sender, mut receiver)) = host.next().await { + tokio::spawn(async move { + let conn = OmikronConnection::new(sender); + conn.handle(&mut receiver).await; + }); + } + + Ok(()) +} diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs new file mode 100644 index 0000000..fbee4a9 --- /dev/null +++ b/src/transport/omikron_manager.rs @@ -0,0 +1,39 @@ +use crate::transport::omikron_connection::OmikronConnection; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::prelude::IteratorRandom; +use std::sync::Arc; + +pub static OMIKRON_CONNECTIONS: Lazy>> = + Lazy::new(|| DashMap::new()); + +pub async fn add_omikron(conn: Arc) { + let id = match conn.clone().get_omikron_id().await { + Some(id) => id, + _ => { + conn.close().await; + return; + } + }; + + if let Some(old) = OMIKRON_CONNECTIONS.insert(id, conn.clone()) { + old.close().await; + } +} + +pub async fn remove_omikron(omikron_id: i64) { + OMIKRON_CONNECTIONS.remove(&omikron_id); +} +pub async fn get_random_omikron() -> Result, ()> { + let mut rng = rand::thread_rng(); + + let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); + + if let Some(key) = keys.into_iter().choose(&mut rng) { + if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { + return Ok(entry.clone()); + } + } + + Err(()) +} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs new file mode 100644 index 0000000..68e3855 --- /dev/null +++ b/src/util/crypto_helper.rs @@ -0,0 +1,137 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, OsRng}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use rand_core::RngCore; +use sha2::{Digest, Sha256}; +use x448::{PublicKey, Secret, SharedSecret}; + +/// Errors for crypto operations +#[derive(Debug)] +pub enum CryptoError { + Base64Decode, + AgreementError, + EncryptionError(aes_gcm::Error), + DecryptionError(aes_gcm::Error), +} + +impl From for CryptoError { + fn from(_: base64::DecodeError) -> Self { + CryptoError::Base64Decode + } +} + +pub fn generate_keypair() -> (Secret, PublicKey) { + let mut buf = [0u8; 56]; + let mut rng = OsRng; + rng.fill_bytes(&mut buf); + let secret = Secret::from_bytes(&buf).unwrap(); + let public = PublicKey::from(&secret); + (secret, public) +} + +pub fn public_key_to_base64(pubkey: &PublicKey) -> String { + STANDARD.encode(pubkey.as_bytes().as_ref()) +} + +pub fn secret_key_to_base64(secret: &Secret) -> String { + STANDARD.encode(secret.as_bytes().as_ref()) +} + +pub fn load_public_key(base64_pub: &str) -> Option { + let bytes = STANDARD.decode(base64_pub).unwrap(); + PublicKey::from_bytes(&bytes) +} + +pub fn load_secret_key(base64_secret: &str) -> Option { + let bytes = STANDARD.decode(base64_secret).unwrap(); + Secret::from_bytes(&bytes) +} + +fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(shared.as_bytes()); + let result = hasher.finalize(); + let mut key = [0u8; 32]; + key.copy_from_slice(&result[..32]); + key +} + +pub fn encrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + plaintext: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + encrypt(secret, peer_pub, plaintext) +} +pub fn encrypt( + secret: Secret, + peer_pub: PublicKey, + plaintext: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + let mut nonce_bytes = [0u8; 12]; + OsRng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + let ciphertext = cipher + .encrypt(nonce, plaintext.as_bytes()) + .map_err(CryptoError::EncryptionError)?; + // prefix nonce to ciphertext + let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); + out.extend_from_slice(&nonce_bytes); + out.extend_from_slice(&ciphertext); + Ok(STANDARD.encode(&out)) +} + +pub fn decrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + encrypted_base64: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + decrypt(secret, peer_pub, encrypted_base64) +} +pub fn decrypt( + secret: Secret, + peer_pub: PublicKey, + encrypted_base64: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + + let encrypted = STANDARD.decode(encrypted_base64)?; + if encrypted.len() < 12 { + return Err(CryptoError::DecryptionError(aes_gcm::Error)); + } + let nonce_bytes = &encrypted[..12]; + let ciphertext = &encrypted[12..]; + let nonce = Nonce::from_slice(nonce_bytes); + let plaintext_bytes = cipher + .decrypt(nonce, ciphertext) + .map_err(CryptoError::DecryptionError)?; + let plaintext = String::from_utf8(plaintext_bytes) + .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; + Ok(plaintext) +} + +pub fn hash_it(input: &str) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(input.as_bytes()); + hasher.finalize().to_vec() +} + +pub fn hex_hash(input: &str) -> String { + let digest = hash_it(input); + digest.iter().map(|b| format!("{:02x}", b)).collect() +} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs new file mode 100644 index 0000000..97acab4 --- /dev/null +++ b/src/util/crypto_util.rs @@ -0,0 +1,201 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, Payload}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; +use hkdf::Hkdf; +use sha2::{Digest, Sha256}; +use x448::{PublicKey, Secret}; + +// --- Custom Errors --- +#[derive(Debug)] +pub enum SecurePayloadError { + InvalidBase64, + InvalidHex, + EncryptionError, + DecryptionError, + InvalidKeyLength, +} + +// --- Data Format Enum --- +#[derive(Clone, Copy, Debug)] +pub enum DataFormat { + Raw, + Base64, + Hex, +} + +// --- Main Class Structure --- +pub struct SecurePayload { + /// The internal canonical representation is always raw bytes. + inner_data: Vec, + /// The private key of the user associated with this payload instance. + private_key: Secret, +} + +impl Clone for SecurePayload { + fn clone(&self) -> Self { + Self { + inner_data: self.inner_data.clone(), + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + } + } +} + +impl SecurePayload { + /// Clear Constructor: Takes data in any format and the user's private key. + pub fn new>( + data: T, + format: DataFormat, + private_key: S, + ) -> Result + where + S: Into, + { + let raw_data = match format { + DataFormat::Raw => data.as_ref().to_vec(), + DataFormat::Base64 => BASE64_STD + .decode(data.as_ref()) + .map_err(|_| SecurePayloadError::InvalidBase64)?, + DataFormat::Hex => { + hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? + } + }; + + Ok(Self { + inner_data: raw_data, + private_key: private_key.into(), + }) + } + + /// Helper to get the public key associated with this instance's private key. + pub fn get_public_key(&self) -> [u8; 56] { + *PublicKey::from(&self.private_key).as_bytes() + } + + /// Exports the internal data to the requested format + pub fn export(&self, format: DataFormat) -> String { + match format.into() { + DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), + DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), + DataFormat::Hex => hex::encode(&self.inner_data), + } + } + + /// Access raw bytes directly + pub fn get_bytes(&self) -> &[u8] { + &self.inner_data + } + + /// Returns the SHA-256 Hash of the data in the requested format + pub fn get_hash(&self, format: DataFormat) -> String { + let mut hasher = Sha256::new(); + hasher.update(&self.inner_data); + let result = hasher.finalize(); + + match format { + DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), + DataFormat::Base64 => BASE64_STD.encode(result), + DataFormat::Hex => hex::encode(result), + } + } + + /// Encrypts the held data for a specific recipient using AES-256-GCM. + /// The message will contain ONLY the ciphertext. + pub fn encrypt_x448(&self, public_key: S) -> Result + where + S: Into, + { + let peer_pub = public_key.into(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + println!( + "Encryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 3. Key & Nonce Derivation (HKDF) + // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 4. Encrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let ciphertext = cipher + .encrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + // 5. Result is ONLY the ciphertext. No key or nonce is packed. + Ok(SecurePayload { + inner_data: ciphertext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } + + /// Decrypts the held data providing the sender's public key manually. + pub fn decrypt_to_format( + &self, + peer_public_key_bytes: &[u8; 56], + output_format: DataFormat, + ) -> Result { + let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; + Ok(decrypted_instance.export(output_format)) + } + + /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. + pub fn decrypt_x448( + &self, + peer_public_key_bytes: &[u8; 56], + ) -> Result { + // 1. Perform Exchange + let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + // LOGGING: Shared Secret + println!( + "Decryption Shared Secret (Hex): {}", + hex::encode(shared_secret.as_bytes()) + ); + + // 2. Key & Nonce Derivation (Must match encryption exactly) + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + // 3. Decrypt with AES-256-GCM + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let plaintext = cipher + .decrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + Ok(SecurePayload { + inner_data: plaintext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } +} diff --git a/src/util/file_util.rs b/src/util/file_util.rs new file mode 100644 index 0000000..b108810 --- /dev/null +++ b/src/util/file_util.rs @@ -0,0 +1,289 @@ +use std::fs::{self, File}; +use std::io::{self, BufReader, Read}; +use std::path::{Path, PathBuf}; +use tokio::io::AsyncWriteExt; +use uuid::Uuid; +use zip::ZipArchive; + +use crate::log; + +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() +} + +#[allow(dead_code)] +pub fn delete_directory(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + delete_dir_recursive(&dir) +} + +#[allow(dead_code)] +fn delete_dir_recursive(directory: &Path) -> bool { + if !directory.exists() { + return false; + } + if let Err(e) = fs::remove_dir_all(directory) { + log!( + "[IMPORTANT] Couldn't delete directory {}: {}", + directory.display(), + e + ); + return false; + } + true +} + +#[allow(dead_code)] +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_buf(path: &str, name: &str) -> io::Result> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + // Ensure the directory exists, create if necessary + if !dir.exists() { + if let Err(_) = fs::create_dir_all(&dir) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Directory creation failed", + )); + } + } + + // Create the file if it doesn't exist + if !file_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "File creation failed", + )); + } + + // Open the file and return a BufReader for efficient reading + let file = File::open(&file_path)?; + Ok(BufReader::new(file)) +} +pub fn has_file(path: &str, name: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + return false; + } + + if !file_path.exists() { + return false; + } + + true +} +pub fn has_dir(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + + if !dir.exists() { + return false; + } + + true +} + +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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return String::new(); + } + return String::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log!("[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 load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + std::fs::read(file_path) +} + +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) { + log!("[IMPORTANT] Couldn't create directories: {}", e); + return; + } + } + + if let Err(e) = fs::write(&file_path, value) { + log!( + "[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() +} + +// Helper to download the zip file content to a file on disk +#[allow(dead_code)] +async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { + let response = reqwest::get(url).await?; + + // Check for successful response status + if !response.status().is_success() { + return Err(format!("Failed to download file: Status {}", response.status()).into()); + } + + let mut zip_file = tokio::fs::File::create(as_name).await?; + let body = response.bytes().await?; + zip_file.write_all(&body).await?; + + Ok(()) +} + +#[allow(dead_code, deprecated)] +fn extract_zip_contents_to_folder( + zip_path: &Path, + target_dir: &Path, +) -> Result<(), Box> { + let file = File::open(zip_path)?; + let mut archive = ZipArchive::new(file)?; + + let staging_dir = target_dir.with_extension("staging"); + + let _ = fs::remove_dir_all(&staging_dir); + fs::create_dir_all(&staging_dir)?; + + let mut first_item_name: Option = None; + + for i in 0..archive.len() { + let mut file = archive.by_index(i)?; + let entry_path = staging_dir.join(file.sanitized_name()); + + if i == 0 { + if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { + first_item_name = Some(file.sanitized_name()); + } + } + + if file.name().ends_with('/') { + fs::create_dir_all(&entry_path)?; + } else { + if let Some(parent) = entry_path.parent() { + fs::create_dir_all(parent)?; + } + let mut out_file = File::create(entry_path)?; + io::copy(&mut file, &mut out_file)?; + } + } + + if let Some(root_path) = first_item_name { + let root_dir = staging_dir.join(&root_path); + + if root_dir.is_dir() { + let root_contents_count = fs::read_dir(&staging_dir)?.count(); + + if root_contents_count == 1 + || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) + { + let _ = fs::remove_dir_all(target_dir); + fs::create_dir_all(target_dir)?; + + for entry in fs::read_dir(root_dir)? { + let entry = entry?; + let src = entry.path(); + let dest = target_dir.join(entry.file_name()); + + if let Err(_) = fs::rename(&src, &dest) { + if src.is_file() { + fs::copy(&src, &dest)?; + } else { + if entry.path().is_dir() { + fs::rename(&src, &dest)?; + } + } + } + } + + let _ = fs::remove_dir_all(&staging_dir); + return Ok(()); + } + } + } + + log!("Extracting directly (no single root folder detected)."); + let _ = fs::remove_dir_all(target_dir); + fs::rename(&staging_dir, target_dir)?; + + Ok(()) +} + +#[allow(dead_code)] +pub async fn download_and_extract_zip(url: &str, as_name: &str) { + let base_dir = PathBuf::from(get_directory()); + let zip_filename = format!("{}.zip", Uuid::new_v4()); + let zip_path = base_dir.join(&zip_filename); + let target_dir = base_dir.join(as_name); + + if let Err(e) = download_zip(url, &zip_path).await { + log!("Error downloading file: {}", e); + return; + } + + let zip_path_clone = zip_path.clone(); + let target_dir_clone = target_dir.clone(); + let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone); + if let Err(e) = extract_result { + log!("Panic during ZIP extraction: {}", e); + } + + if let Err(e) = tokio::fs::remove_file(&zip_path).await { + log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs new file mode 100644 index 0000000..d1a7764 --- /dev/null +++ b/src/util/logger.rs @@ -0,0 +1,352 @@ +use std::{ + collections::{BTreeMap, HashMap}, + fs::{self, OpenOptions}, + io::Write, + path::Path, + sync::{OnceLock, mpsc}, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; + +use ansi_term::Color; +use epsilon_core::{CommunicationValue, DataTypes, DataValue}; +use json::JsonValue; + +static LOGGER: OnceLock> = OnceLock::new(); + +#[derive(Clone, Copy)] +#[allow(unused)] +pub enum PrintType { + Call, + Client, + Iota, + Omikron, + Omega, + General, +} + +struct LogMessage { + timestamp_ms: u128, + sender: Option, + prefix: &'static str, + kind: PrintType, + is_error: bool, + message: String, +} + +pub fn startup() { + let (tx, rx) = mpsc::channel::(); + LOGGER.set(tx).expect("Logger already initialized"); + + thread::spawn(move || { + let log_dir = Path::new("logs"); + fs::create_dir_all(log_dir).expect("Failed to create log directory"); + + let start_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let path = log_dir.join(format!("log_{}.txt", start_ts)); + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .expect("Failed to open log file"); + + for msg in rx { + let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); + let sender = match msg.sender { + Some(id) => fixed_box(&id.to_string(), 19), + _ => fixed_box("", 19), + }; + + let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); + + println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); + + let _ = writeln!(file, "{}", line); + } + }); +} + +fn colorize(kind: PrintType, is_error: bool) -> Color { + if is_error { + return Color::Red; + } + + match kind { + PrintType::Call => Color::Purple, + PrintType::Client => Color::Green, + PrintType::Iota => Color::Yellow, + PrintType::Omikron => Color::Blue, + PrintType::Omega => Color::Cyan, + PrintType::General => Color::White, + } +} + +fn fixed_box(content: &str, width: usize) -> String { + let s: String = content.chars().take(width).collect(); + let len = s.chars().count(); + if len < width { + format!("[{}{}]", " ".repeat(width - len), s) + } else { + s + } +} + +pub fn log_internal( + sender: Option, + kind: PrintType, + prefix: &'static str, + is_error: bool, + message: String, +) { + if let Some(tx) = LOGGER.get() { + let _ = tx.send(LogMessage { + timestamp_ms: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + sender, + prefix, + kind, + is_error, + message, + }); + } +} + +#[macro_export] +macro_rules! log { + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + "", + false, + format!($($arg)*) + ) + }; + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! log_in { + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + ">", + false, + format!($($arg)*) + ) + }; +} + +#[macro_export] +macro_rules! log_out { + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + "<", + false, + format!($($arg)*) + ) + }; +} + +#[macro_export] +macro_rules! log_err { + + // sender + actor + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) + }; + + // actor only + ($kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) + }; + + // plain + ($($arg:tt)*) => { + $crate::util::logger::log_internal( + None, + $crate::util::logger::PrintType::General, + ">>", + true, + format!($($arg)*) + ) + }; +} + +// ******** COMMUNICATION VALUES ******** +pub fn log_cv_internal( + prefix: &'static str, + cv: &CommunicationValue, + print_type: Option, +) { + let formatted = format_cv(cv); + + log_internal( + Some(cv.get_sender() as i64), + print_type.unwrap_or(PrintType::General), + prefix, + false, + formatted, + ); +} + +pub fn format_cv(cv: &CommunicationValue) -> String { + let mut parts = Vec::new(); + + let sender = cv.get_sender(); + let receiver = cv.get_receiver(); + + if sender > 0 && receiver > 0 { + parts.push(format!("{} > {}", sender, receiver)); + } else if sender > 0 { + parts.push(format!("{}", sender)); + } else if receiver > 0 { + parts.push(format!("> {}", receiver)); + } + + let comm_type = cv.get_type().to_string(); + parts.push(format!("{}", comm_type)); + + let data: &BTreeMap = cv.get_data_container(); + + let formated_data = + format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); + + parts.push(format!("{}", formated_data)); + + parts.join(": ") +} + +fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { + let parts: Vec = data + .into_iter() + .map(|(key, value)| { + let key_str = key.to_string(); + + match value { + DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{}={{ {} }}", key_str, inner_formatted) + } + + DataValue::Array(arr) => { + let arr_formatted = format_array(arr); + format!("{}=[{}]", key_str, arr_formatted) + } + + DataValue::Bool(b) => format!("{}={}", key_str, b), + + DataValue::BoolTrue => format!("{}=true", key_str), + DataValue::BoolFalse => format!("{}=false", key_str), + + DataValue::Number(num) => format!("{}={}", key_str, num), + + _ => "".to_string(), + } + }) + .collect(); + + parts.join(", ") +} + +fn format_array(arr: Vec) -> String { + let parts: Vec = arr + .into_iter() + .map(|value| match value { + DataValue::Str(s) => format!("\"{}\"", s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{{ {} }}", inner_formatted) + } + + DataValue::Array(inner_arr) => { + let formatted = format_array(inner_arr); + format!("[{}]", formatted) + } + + DataValue::Bool(b) => b.to_string(), + + DataValue::BoolTrue => "true".to_string(), + DataValue::BoolFalse => "false".to_string(), + + DataValue::Number(num) => num.to_string(), + + _ => String::new(), + }) + .collect(); + + parts.join(", ") +} +#[macro_export] +macro_rules! log_cv { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, None) + }; +} + +#[macro_export] +macro_rules! log_cv_in { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, None) + }; +} +#[macro_export] +macro_rules! log_cv_out { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, None) + }; +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..1897223 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,4 @@ +pub mod crypto_helper; +pub mod crypto_util; +pub mod file_util; +pub mod logger; From 0576558d6efe113430cf689df14bc793414bf94a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 2 Apr 2026 23:30:09 +0200 Subject: [PATCH 105/220] [Add] README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e210a3..a1d4baf 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omega) ⚠️ +# Omega +The Omega is Tensamin's central Server. It maintains the centralized user Registry & manages Omikron useage. From 32eae5bc51e8c651f557041cb9158677b1bae06f Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 2 Apr 2026 23:30:09 +0200 Subject: [PATCH 106/220] [Add] README --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0e210a3..a1d4baf 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omega) ⚠️ +# Omega +The Omega is Tensamin's central Server. It maintains the centralized user Registry & manages Omikron useage. From 760317c9a8c17e7613ef5380afbc89326f010bba Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 5 Apr 2026 21:48:47 +0200 Subject: [PATCH 107/220] [Fix] Changed to TTP, changed remote to fetch from git.methanium.net --- Cargo.lock | 294 +++++++++++++++------------- Cargo.toml | 4 +- src/transport/omikron_connection.rs | 8 +- src/util/logger.rs | 2 +- 4 files changed, 164 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1444c40..44f713f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,8 +13,6 @@ dependencies = [ "base64", "dashmap", "dotenv", - "epsilon-core", - "epsilon-native", "hex", "hkdf", "json", @@ -30,6 +28,8 @@ dependencies = [ "strum_macros 0.27.2", "thiserror 2.0.18", "tokio", + "ttp-core", + "ttp-native", "uuid", "x448", "zip", @@ -404,7 +404,7 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", + "fastrand 2.4.0", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -538,9 +538,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -549,9 +549,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -707,9 +707,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -881,9 +881,9 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deflate64" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" [[package]] name = "der" @@ -1023,32 +1023,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "epsilon-core" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "base64", - "byteorder", - "rand 0.8.5", - "strum 0.28.0", - "strum_macros 0.28.0", -] - -[[package]] -name = "epsilon-native" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "epsilon-core", - "quinn", - "rustls", - "rustls-native-certs", - "thiserror 2.0.18", - "tokio", - "wtransport", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1114,9 +1088,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" [[package]] name = "fiat-crypto" @@ -1244,7 +1218,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.0", "futures-core", "futures-io", "parking", @@ -1535,9 +1509,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1549,7 +1523,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1598,12 +1571,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1611,9 +1585,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1624,9 +1598,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1638,15 +1612,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1658,15 +1632,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1712,9 +1686,9 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1759,9 +1733,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -1769,9 +1743,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" @@ -1782,7 +1756,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -1791,9 +1765,31 @@ dependencies = [ [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -1807,10 +1803,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1859,9 +1857,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" @@ -1871,9 +1869,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "bitflags 2.11.0", "libc", @@ -1905,9 +1903,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "local-channel" @@ -2000,9 +1998,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -2048,9 +2046,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -2198,7 +2196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand 2.4.0", "futures-io", ] @@ -2279,9 +2277,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2608,9 +2606,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -2733,9 +2731,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -2804,9 +2802,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -2913,9 +2911,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" @@ -3353,9 +3351,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3378,9 +3376,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", @@ -3395,9 +3393,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3510,6 +3508,32 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttp-core" +version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +dependencies = [ + "base64", + "byteorder", + "rand 0.8.5", + "strum 0.28.0", + "strum_macros 0.28.0", +] + +[[package]] +name = "ttp-native" +version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +dependencies = [ + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "ttp-core", + "wtransport", +] + [[package]] name = "typenum" version = "1.19.0" @@ -3545,9 +3569,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -3597,9 +3621,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3681,9 +3705,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -3694,23 +3718,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3718,9 +3738,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -3731,9 +3751,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -3774,9 +3794,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -4255,9 +4275,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wtransport" @@ -4352,9 +4372,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4363,9 +4383,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4375,18 +4395,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4395,18 +4415,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4436,9 +4456,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4447,9 +4467,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4458,9 +4478,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 34673fb..5e958ff 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } -epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } +ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } +ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 504d723..69d9691 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -11,8 +11,6 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use epsilon_native::{Host, Receiver, Sender}; use rand::{Rng, distributions::Alphanumeric}; use std::{ sync::Arc, @@ -22,6 +20,8 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Host, Receiver, Sender}; use x448::PublicKey; // ============================================================================ @@ -92,7 +92,7 @@ impl AuthState { } // ============================================================================ -// Omikron Connection (Epsilon/QUIC-based) +// Omikron Connection (ttp/QUIC-based) // ============================================================================ pub struct OmikronConnection { @@ -1125,7 +1125,7 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; + let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); while let Some((sender, mut receiver)) = host.next().await { diff --git a/src/util/logger.rs b/src/util/logger.rs index d1a7764..e2e6c92 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -9,8 +9,8 @@ use std::{ }; use ansi_term::Color; -use epsilon_core::{CommunicationValue, DataTypes, DataValue}; use json::JsonValue; +use ttp_core::{CommunicationValue, DataTypes, DataValue}; static LOGGER: OnceLock> = OnceLock::new(); From 56d21b6d38207d08c247842d7074c7bf99bfd102 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 5 Apr 2026 21:48:47 +0200 Subject: [PATCH 108/220] [Fix] Changed to TTP, changed remote to fetch from git.methanium.net --- Cargo.lock | 294 +++++++++++++++------------- Cargo.toml | 4 +- src/transport/omikron_connection.rs | 8 +- src/util/logger.rs | 2 +- 4 files changed, 164 insertions(+), 144 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1444c40..44f713f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,8 +13,6 @@ dependencies = [ "base64", "dashmap", "dotenv", - "epsilon-core", - "epsilon-native", "hex", "hkdf", "json", @@ -30,6 +28,8 @@ dependencies = [ "strum_macros 0.27.2", "thiserror 2.0.18", "tokio", + "ttp-core", + "ttp-native", "uuid", "x448", "zip", @@ -404,7 +404,7 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.3.0", + "fastrand 2.4.0", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -538,9 +538,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.1" +version = "1.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bffc006df10ac2a68c83692d734a465f8ee6c5b384d8545a636f81d858f4bf" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -549,9 +549,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.38.0" +version = "0.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4321e568ed89bb5a7d291a7f37997c2c0df89809d7b6d12062c81ddb54aa782e" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" dependencies = [ "cc", "cmake", @@ -667,9 +667,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.57" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ "find-msvc-tools", "jobserver", @@ -707,9 +707,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ "cc", ] @@ -881,9 +881,9 @@ checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "deflate64" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "807800ff3288b621186fe0a8f3392c4652068257302709c24efd918c3dffcdc2" +checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" [[package]] name = "der" @@ -1023,32 +1023,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "epsilon-core" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "base64", - "byteorder", - "rand 0.8.5", - "strum 0.28.0", - "strum_macros 0.28.0", -] - -[[package]] -name = "epsilon-native" -version = "0.1.0" -source = "git+https://github.com/Tensamin/Epsilon.git#d5edc693a91954834a38c39517b79092983eac5c" -dependencies = [ - "epsilon-core", - "quinn", - "rustls", - "rustls-native-certs", - "thiserror 2.0.18", - "tokio", - "wtransport", -] - [[package]] name = "equivalent" version = "1.0.2" @@ -1114,9 +1088,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.3.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" [[package]] name = "fiat-crypto" @@ -1244,7 +1218,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.3.0", + "fastrand 2.4.0", "futures-core", "futures-io", "parking", @@ -1535,9 +1509,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "1.8.1" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ "atomic-waker", "bytes", @@ -1549,7 +1523,6 @@ dependencies = [ "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -1598,12 +1571,13 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -1611,9 +1585,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -1624,9 +1598,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1638,15 +1612,15 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ "icu_collections", "icu_locale_core", @@ -1658,15 +1632,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "2.1.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", "icu_locale_core", @@ -1712,9 +1686,9 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1759,9 +1733,9 @@ checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" [[package]] name = "iri-string" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" dependencies = [ "memchr", "serde", @@ -1769,9 +1743,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" @@ -1782,7 +1756,7 @@ dependencies = [ "cesu8", "cfg-if", "combine", - "jni-sys", + "jni-sys 0.3.1", "log", "thiserror 1.0.69", "walkdir", @@ -1791,9 +1765,31 @@ dependencies = [ [[package]] name = "jni-sys" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn", +] [[package]] name = "jobserver" @@ -1807,10 +1803,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ + "cfg-if", + "futures-util", "once_cell", "wasm-bindgen", ] @@ -1859,9 +1857,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.184" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" [[package]] name = "libm" @@ -1871,9 +1869,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1744e39d1d6a9948f4f388969627434e31128196de472883b39f148769bfe30a" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ "bitflags 2.11.0", "libc", @@ -1905,9 +1903,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "local-channel" @@ -2000,9 +1998,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -2048,9 +2046,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" [[package]] name = "num-integer" @@ -2198,7 +2196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.3.0", + "fastrand 2.4.0", "futures-io", ] @@ -2279,9 +2277,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] @@ -2608,9 +2606,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" @@ -2733,9 +2731,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "aws-lc-rs", "ring", @@ -2804,9 +2802,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -2913,9 +2911,9 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" [[package]] name = "slab" @@ -3353,9 +3351,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", @@ -3378,9 +3376,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.50.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ "bytes", "libc", @@ -3395,9 +3393,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", @@ -3510,6 +3508,32 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttp-core" +version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +dependencies = [ + "base64", + "byteorder", + "rand 0.8.5", + "strum 0.28.0", + "strum_macros 0.28.0", +] + +[[package]] +name = "ttp-native" +version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +dependencies = [ + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "ttp-core", + "wtransport", +] + [[package]] name = "typenum" version = "1.19.0" @@ -3545,9 +3569,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -3597,9 +3621,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.22.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3681,9 +3705,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", "once_cell", @@ -3694,23 +3718,19 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.64" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9c5522b3a28661442748e09d40924dfb9ca614b21c00d3fd135720e48b67db8" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ - "cfg-if", - "futures-util", "js-sys", - "once_cell", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3718,9 +3738,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ "bumpalo", "proc-macro2", @@ -3731,9 +3751,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" dependencies = [ "unicode-ident", ] @@ -3774,9 +3794,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -4255,9 +4275,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wtransport" @@ -4352,9 +4372,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4363,9 +4383,9 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", @@ -4375,18 +4395,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", @@ -4395,18 +4415,18 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", @@ -4436,9 +4456,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", @@ -4447,9 +4467,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.5" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -4458,9 +4478,9 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 34673fb..5e958ff 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -epsilon-core = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-core" } -epsilon-native = { git = "https://github.com/Tensamin/Epsilon.git", package = "epsilon-native" } +ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } +ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 504d723..69d9691 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -11,8 +11,6 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use epsilon_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use epsilon_native::{Host, Receiver, Sender}; use rand::{Rng, distributions::Alphanumeric}; use std::{ sync::Arc, @@ -22,6 +20,8 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Host, Receiver, Sender}; use x448::PublicKey; // ============================================================================ @@ -92,7 +92,7 @@ impl AuthState { } // ============================================================================ -// Omikron Connection (Epsilon/QUIC-based) +// Omikron Connection (ttp/QUIC-based) // ============================================================================ pub struct OmikronConnection { @@ -1125,7 +1125,7 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = epsilon_native::host(port, cert_pem, key_pem).await?; + let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; log!("OmikronServer listening on port {}", port); while let Some((sender, mut receiver)) = host.next().await { diff --git a/src/util/logger.rs b/src/util/logger.rs index d1a7764..e2e6c92 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -9,8 +9,8 @@ use std::{ }; use ansi_term::Color; -use epsilon_core::{CommunicationValue, DataTypes, DataValue}; use json::JsonValue; +use ttp_core::{CommunicationValue, DataTypes, DataValue}; static LOGGER: OnceLock> = OnceLock::new(); From ee3cceb26b1fe690f39e9c161e1decd8deb8f194 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:08:58 +0200 Subject: [PATCH 109/220] [Fix] now correct iota ID loading --- Cargo.lock | 18 ++-- src/transport/omikron_connection.rs | 122 ++++++++++++---------------- 2 files changed, 61 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44f713f..3b0c59a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,7 +404,7 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.4.0", + "fastrand 2.4.1", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -1088,9 +1088,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fiat-crypto" @@ -1218,7 +1218,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.4.0", + "fastrand 2.4.1", "futures-core", "futures-io", "parking", @@ -2196,7 +2196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.4.0", + "fastrand 2.4.1", "futures-io", ] @@ -3376,9 +3376,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ "bytes", "libc", @@ -3511,7 +3511,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" dependencies = [ "base64", "byteorder", @@ -3523,7 +3523,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" dependencies = [ "quinn", "rustls", diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 69d9691..327c52c 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -433,44 +433,40 @@ impl OmikronConnection { async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + let iota_id = cv.get_sender(); + let iota_id = iota_id as i64; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id)); - user_online_tracker::track_user_status( - user_id, - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error loading users for IOTA"); + let mut user_ids = Vec::new(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { + user_ids.push(DataValue::Number(user_id)); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_offline, + omikron_id, + ); } - - let response = CommunicationValue::new(CommunicationType::iota_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); - - let _ = self.send(&response).await; } else { - log_in!(PrintType::General, "No IOTA ID found"); + log_in!(PrintType::General, "SQL error loading users for IOTA"); } + + let response = CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + + let _ = self.send(&response).await; } async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } + let iota_id = cv.get_sender(); + let iota_id = iota_id as i64; + let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); } } } @@ -616,14 +612,13 @@ impl OmikronConnection { async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; - } + let iota_id = cv.get_sender(); + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .clone() + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; } // Try by user_id @@ -780,19 +775,16 @@ impl OmikronConnection { .get_data(DataTypes::public_key) .as_str() .map(|s| s.to_string()); - let iota_id = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); + let iota_id = cv.get_sender(); let reset_token = cv .get_data(DataTypes::reset_token) .as_str() .map(|s| s.to_string()); - if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = - (user_id, username, public_key, iota_id, reset_token) + if let (Some(uid), Some(uname), Some(pk), Some(rt)) = + (user_id, username, public_key, reset_token) { - match sql::register_complete_user(uid, uname, pk, iid, rt).await { + match sql::register_complete_user(uid, uname, pk, iota_id as i64, rt).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); @@ -877,10 +869,8 @@ impl OmikronConnection { async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; - if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64), + if let (iota_id, Some(reset_token), Some(new_token)) = ( + cv.get_sender(), cv.get_data(DataTypes::reset_token).as_str(), cv.get_data(DataTypes::new_token).as_str(), ) { @@ -891,7 +881,7 @@ impl OmikronConnection { let mut success = true; let mut error_message = String::new(); - if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + if let Err(e) = sql::change_iota_id(user_id, iota_id as i64).await { success = false; error_message = e.to_string(); } @@ -950,27 +940,19 @@ impl OmikronConnection { } async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(iota_id) = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64) - { - match sql::delete_iota(iota_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } + let iota_id = cv.get_sender(); + match sql::delete_iota(iota_id as i64).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await } } From 75b0c0545523114cb6679dedea5a2ca4f2d4b7a9 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 9 Apr 2026 20:08:58 +0200 Subject: [PATCH 110/220] [Fix] now correct iota ID loading --- Cargo.lock | 18 ++-- src/transport/omikron_connection.rs | 122 ++++++++++++---------------- 2 files changed, 61 insertions(+), 79 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44f713f..3b0c59a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -404,7 +404,7 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.4.0", + "fastrand 2.4.1", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -1088,9 +1088,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "fiat-crypto" @@ -1218,7 +1218,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.4.0", + "fastrand 2.4.1", "futures-core", "futures-io", "parking", @@ -2196,7 +2196,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.4.0", + "fastrand 2.4.1", "futures-io", ] @@ -3376,9 +3376,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.0" +version = "1.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" +checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" dependencies = [ "bytes", "libc", @@ -3511,7 +3511,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" dependencies = [ "base64", "byteorder", @@ -3523,7 +3523,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#82b71c1be1fa73aeb0f228c4af364206fda8d712" +source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" dependencies = [ "quinn", "rustls", diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 69d9691..327c52c 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -433,44 +433,40 @@ impl OmikronConnection { async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + let iota_id = cv.get_sender(); + let iota_id = iota_id as i64; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id)); - user_online_tracker::track_user_status( - user_id, - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error loading users for IOTA"); + let mut user_ids = Vec::new(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { + user_ids.push(DataValue::Number(user_id)); + user_online_tracker::track_user_status( + user_id, + UserStatus::user_offline, + omikron_id, + ); } - - let response = CommunicationValue::new(CommunicationType::iota_user_data) - .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); - - let _ = self.send(&response).await; } else { - log_in!(PrintType::General, "No IOTA ID found"); + log_in!(PrintType::General, "SQL error loading users for IOTA"); } + + let response = CommunicationValue::new(CommunicationType::iota_user_data) + .with_id(cv.get_id()) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + + let _ = self.send(&response).await; } async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - let iota_id = iota_id as i64; - let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } + let iota_id = cv.get_sender(); + let iota_id = iota_id as i64; + let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); + if iota_offline { + if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + let user_ids: Vec = users.iter().map(|u| u.0).collect(); + user_online_tracker::untrack_many_users(&user_ids); } } } @@ -616,14 +612,13 @@ impl OmikronConnection { async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; - } + let iota_id = cv.get_sender(); + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .clone() + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; } // Try by user_id @@ -780,19 +775,16 @@ impl OmikronConnection { .get_data(DataTypes::public_key) .as_str() .map(|s| s.to_string()); - let iota_id = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); + let iota_id = cv.get_sender(); let reset_token = cv .get_data(DataTypes::reset_token) .as_str() .map(|s| s.to_string()); - if let (Some(uid), Some(uname), Some(pk), Some(iid), Some(rt)) = - (user_id, username, public_key, iota_id, reset_token) + if let (Some(uid), Some(uname), Some(pk), Some(rt)) = + (user_id, username, public_key, reset_token) { - match sql::register_complete_user(uid, uname, pk, iid, rt).await { + match sql::register_complete_user(uid, uname, pk, iota_id as i64, rt).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); @@ -877,10 +869,8 @@ impl OmikronConnection { async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; - if let (Some(iota_id), Some(reset_token), Some(new_token)) = ( - cv.get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64), + if let (iota_id, Some(reset_token), Some(new_token)) = ( + cv.get_sender(), cv.get_data(DataTypes::reset_token).as_str(), cv.get_data(DataTypes::new_token).as_str(), ) { @@ -891,7 +881,7 @@ impl OmikronConnection { let mut success = true; let mut error_message = String::new(); - if let Err(e) = sql::change_iota_id(user_id, iota_id).await { + if let Err(e) = sql::change_iota_id(user_id, iota_id as i64).await { success = false; error_message = e.to_string(); } @@ -950,27 +940,19 @@ impl OmikronConnection { } async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(iota_id) = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64) - { - match sql::delete_iota(iota_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); - self.send(&response).await - } + let iota_id = cv.get_sender(); + match sql::delete_iota(iota_id as i64).await { + Ok(_) => { + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send(&response).await + } + Err(e) => { + let response = CommunicationValue::new(CommunicationType::error) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + self.send(&response).await } - } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) - .await } } From c41860509ad28ee8ca12a3f9b6550b66708d581a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:50:50 +0200 Subject: [PATCH 111/220] [Add] Omega Index route --- src/server/index.rs | 17 +++++++++++++++++ src/server/mod.rs | 1 + src/server/server.rs | 3 ++- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/server/index.rs diff --git a/src/server/index.rs b/src/server/index.rs new file mode 100644 index 0000000..1d302b1 --- /dev/null +++ b/src/server/index.rs @@ -0,0 +1,17 @@ +use actix_web::{HttpResponse, Responder}; + +pub async fn index_handler() -> impl Responder { + let documentation = r#" +Omega API Server + +Available Routes: +- /api/* : API endpoints for the Omega server. +- /direct/* : Resolution for shortened links. + +All other routes will return this documentation. +"#; + + HttpResponse::Ok() + .content_type("text/plain; charset=utf-8") + .body(documentation) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 8645e6e..59e9769 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,3 +1,4 @@ pub mod api; +pub mod index; pub mod server; pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs index 3618eb7..1fcfe04 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,6 +1,6 @@ use crate::{ log, - server::{api, short_link::get_short_link}, + server::{api, index::index_handler, short_link::get_short_link}, util::file_util::load_file_buf, }; @@ -37,6 +37,7 @@ pub async fn start(port: u16) -> anyhow::Result<()> { App::new() .route("/api/{path:.*}", web::to(api_handler)) .route("/direct/{path:.*}", web::to(direct_handler)) + .default_service(web::to(index_handler)) }) .bind_rustls_0_23(addr, config)? .run() From 00a84ac0d6864bbf0f05f5a74493ec2dda211283 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:50:50 +0200 Subject: [PATCH 112/220] [Add] Omega Index route --- src/server/index.rs | 17 +++++++++++++++++ src/server/mod.rs | 1 + src/server/server.rs | 3 ++- 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/server/index.rs diff --git a/src/server/index.rs b/src/server/index.rs new file mode 100644 index 0000000..1d302b1 --- /dev/null +++ b/src/server/index.rs @@ -0,0 +1,17 @@ +use actix_web::{HttpResponse, Responder}; + +pub async fn index_handler() -> impl Responder { + let documentation = r#" +Omega API Server + +Available Routes: +- /api/* : API endpoints for the Omega server. +- /direct/* : Resolution for shortened links. + +All other routes will return this documentation. +"#; + + HttpResponse::Ok() + .content_type("text/plain; charset=utf-8") + .body(documentation) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 8645e6e..59e9769 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,3 +1,4 @@ pub mod api; +pub mod index; pub mod server; pub mod short_link; diff --git a/src/server/server.rs b/src/server/server.rs index 3618eb7..1fcfe04 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,6 +1,6 @@ use crate::{ log, - server::{api, short_link::get_short_link}, + server::{api, index::index_handler, short_link::get_short_link}, util::file_util::load_file_buf, }; @@ -37,6 +37,7 @@ pub async fn start(port: u16) -> anyhow::Result<()> { App::new() .route("/api/{path:.*}", web::to(api_handler)) .route("/direct/{path:.*}", web::to(direct_handler)) + .default_service(web::to(index_handler)) }) .bind_rustls_0_23(addr, config)? .run() From cc8c14ca93e3f614837e6b4e85aa9ba66606e7af Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:51:03 +0200 Subject: [PATCH 113/220] [Fix] reverted some iota_id --- src/transport/omikron_connection.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 327c52c..47b42ef 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -433,8 +433,10 @@ impl OmikronConnection { async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); - let iota_id = cv.get_sender(); - let iota_id = iota_id as i64; + let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + Some(id) => id as i64, + None => return, + }; user_online_tracker::track_iota_connection(iota_id, omikron_id, true); let mut user_ids = Vec::new(); @@ -460,8 +462,10 @@ impl OmikronConnection { async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); - let iota_id = cv.get_sender(); - let iota_id = iota_id as i64; + let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + Some(id) => id as i64, + None => return, + }; let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); if iota_offline { if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { @@ -612,13 +616,14 @@ impl OmikronConnection { async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id - let iota_id = cv.get_sender(); - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .clone() + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; + } } // Try by user_id From ad334481b1eb813cca66c7ee9436b5b40064f409 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 10 Apr 2026 21:51:03 +0200 Subject: [PATCH 114/220] [Fix] reverted some iota_id --- src/transport/omikron_connection.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 327c52c..47b42ef 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -433,8 +433,10 @@ impl OmikronConnection { async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); - let iota_id = cv.get_sender(); - let iota_id = iota_id as i64; + let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + Some(id) => id as i64, + None => return, + }; user_online_tracker::track_iota_connection(iota_id, omikron_id, true); let mut user_ids = Vec::new(); @@ -460,8 +462,10 @@ impl OmikronConnection { async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); - let iota_id = cv.get_sender(); - let iota_id = iota_id as i64; + let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + Some(id) => id as i64, + None => return, + }; let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); if iota_offline { if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { @@ -612,13 +616,14 @@ impl OmikronConnection { async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id - let iota_id = cv.get_sender(); - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { + let response = self + .clone() + .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) + .await; + return self.send(&response).await; + } } // Try by user_id From 6873d620d8c37134523160c7d36ea68a11883980 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 3 May 2026 15:00:21 +0200 Subject: [PATCH 115/220] [Fix] Small omega, complete DB controll --- Cargo.lock | 618 +++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- src/sql/sql.rs | 9 +- 3 files changed, 323 insertions(+), 306 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b0c59a..790ecc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,46 +2,13 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Omega" -version = "0.1.0" -dependencies = [ - "actix-web", - "aes-gcm", - "ansi_term", - "anyhow", - "base64", - "dashmap", - "dotenv", - "hex", - "hkdf", - "json", - "once_cell", - "rand 0.8.5", - "rand_core 0.6.4", - "reqwest", - "rustls", - "rustls-pemfile", - "sha2", - "sqlx", - "strum 0.27.2", - "strum_macros 0.27.2", - "thiserror 2.0.18", - "tokio", - "ttp-core", - "ttp-native", - "uuid", - "x448", - "zip", -] - [[package]] name = "actix-codec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-sink", @@ -54,9 +21,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.0" +version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" +checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" dependencies = [ "actix-codec", "actix-rt", @@ -64,7 +31,7 @@ dependencies = [ "actix-tls", "actix-utils", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "brotli", "bytes", "bytestring", @@ -83,8 +50,8 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.2", - "sha1", + "rand 0.10.1", + "sha1 0.11.0", "smallvec", "tokio", "tokio-util", @@ -251,7 +218,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -263,7 +230,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -346,7 +313,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -538,9 +505,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -549,9 +516,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -579,9 +546,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -595,6 +562,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "blocking" version = "1.6.2" @@ -649,9 +625,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bytestring" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ "bytes", ] @@ -667,9 +643,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.59" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -677,12 +653,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -695,13 +665,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] @@ -739,6 +720,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -800,6 +787,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -811,9 +807,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" @@ -850,6 +846,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -875,9 +880,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deflate64" @@ -891,7 +896,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] @@ -959,12 +964,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1298,6 +1314,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1381,9 +1398,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hashlink" @@ -1433,7 +1450,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1507,6 +1524,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.9.0" @@ -1530,15 +1556,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1670,9 +1695,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1686,12 +1711,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1749,27 +1774,32 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys 0.3.1", + "jni-macros", + "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror", "walkdir", - "windows-sys 0.45.0", + "windows-link", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "jni-sys 0.4.1", + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] @@ -1803,9 +1833,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -1851,15 +1881,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libbz2-rs-sys" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" +checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -1869,14 +1899,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -1955,7 +1985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" dependencies = [ "crc", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1965,7 +1995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -2039,7 +2069,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] @@ -2095,6 +2125,39 @@ dependencies = [ "asn1-rs", ] +[[package]] +name = "omega" +version = "0.1.0" +dependencies = [ + "actix-web", + "aes-gcm", + "ansi_term", + "anyhow", + "base64", + "dashmap", + "dotenv", + "hex", + "hkdf", + "json", + "once_cell", + "rand 0.8.6", + "rand_core 0.6.4", + "reqwest", + "rustls", + "rustls-pemfile", + "sha2 0.10.9", + "sqlx", + "strum 0.27.2", + "strum_macros 0.27.2", + "thiserror", + "tokio", + "ttp-core", + "ttp-native", + "uuid", + "x448", + "zip", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2148,7 +2211,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", + "digest 0.10.7", "hmac", ] @@ -2223,9 +2286,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -2270,7 +2333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2338,7 +2401,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.3", - "thiserror 2.0.18", + "thiserror", "tokio", "tracing", "web-time", @@ -2354,13 +2417,13 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2403,9 +2466,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2414,14 +2477,25 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2466,6 +2540,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rcgen" version = "0.14.7" @@ -2475,7 +2555,7 @@ dependencies = [ "aws-lc-rs", "rustls-pki-types", "time", - "x509-parser 0.18.1", + "x509-parser", "yasna", ] @@ -2485,16 +2565,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2534,9 +2614,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64", "bytes", @@ -2590,8 +2670,8 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", @@ -2648,7 +2728,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2657,9 +2737,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -2694,9 +2774,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -2704,9 +2784,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", @@ -2731,9 +2811,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -2783,7 +2863,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2868,8 +2948,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", ] [[package]] @@ -2879,8 +2970,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", ] [[package]] @@ -2905,7 +3007,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -2915,6 +3017,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -3019,9 +3137,9 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", - "thiserror 2.0.18", + "thiserror", "tracing", "url", ] @@ -3055,7 +3173,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -3072,11 +3190,11 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", "bytes", "crc", - "digest", + "digest 0.10.7", "dotenvy", "either", "futures-channel", @@ -3093,15 +3211,15 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand 0.8.6", "rsa", "serde", - "sha1", - "sha2", + "sha1 0.10.6", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror", "tracing", "whoami", ] @@ -3114,7 +3232,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", "crc", "dotenvy", @@ -3131,14 +3249,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror", "tracing", "whoami", ] @@ -3162,7 +3280,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.18", + "thiserror", "tracing", "url", ] @@ -3263,7 +3381,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3278,33 +3396,13 @@ dependencies = [ "libc", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -3376,9 +3474,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.1" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -3446,7 +3544,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http 1.4.0", @@ -3511,11 +3609,12 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" dependencies = [ "base64", "byteorder", - "rand 0.8.5", + "rand 0.8.6", + "serde_json", "strum 0.28.0", "strum_macros 0.28.0", ] @@ -3523,12 +3622,12 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" dependencies = [ "quinn", "rustls", "rustls-native-certs", - "thiserror 2.0.18", + "thiserror", "tokio", "ttp-core", "wtransport", @@ -3536,9 +3635,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-bidi" @@ -3585,7 +3684,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -3621,9 +3720,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3681,11 +3780,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -3694,7 +3793,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -3705,9 +3804,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -3718,9 +3817,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -3728,9 +3827,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3738,9 +3837,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -3751,9 +3850,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -3786,7 +3885,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -3794,9 +3893,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -3814,9 +3913,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -3897,15 +3996,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3942,21 +4032,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -4005,12 +4080,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4029,12 +4098,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4053,12 +4116,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4089,12 +4146,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4113,12 +4164,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4137,12 +4182,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4161,12 +4200,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4194,6 +4227,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -4243,7 +4282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -4281,9 +4320,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wtransport" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" +checksum = "ea4aacf790813ee1956751491800537f4e04af7557b7b370501ccbfbc85963e4" dependencies = [ "bytes", "pem", @@ -4292,26 +4331,26 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-pki-types", - "sha2", - "socket2 0.5.10", - "thiserror 2.0.18", + "sha2 0.11.0", + "socket2 0.6.3", + "thiserror", "time", "tokio", "tracing", "url", "wtransport-proto", - "x509-parser 0.17.0", + "x509-parser", ] [[package]] name = "wtransport-proto" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" +checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror 2.0.18", + "thiserror", "url", ] @@ -4326,23 +4365,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "x509-parser" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - [[package]] name = "x509-parser" version = "0.18.1" @@ -4357,7 +4379,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -4507,7 +4529,7 @@ dependencies = [ "memchr", "pbkdf2", "ppmd-rust", - "sha1", + "sha1 0.10.6", "time", "zeroize", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index 5e958ff..22ec471 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "Omega" +name = "omega" version = "0.1.0" edition = "2024" diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 8445e26..787d7d1 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -23,16 +23,11 @@ use crate::sql::{ static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); pub async fn connect() -> Result, sqlx::Error> { - let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); - let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); - let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); + let url = env::var("DB_URL").expect("DB_URL is not set"); MySqlPoolOptions::new() .max_connections(200) - .connect(&format!( - "mysql://{}:{}@127.0.0.1:3306/{}", - user, passwd, table - )) + .connect(&url) .await } // Omega From d868002e8d2cbdd079d9578e73c382091729439d Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 3 May 2026 15:00:21 +0200 Subject: [PATCH 116/220] [Fix] Small omega, complete DB controll --- Cargo.lock | 618 +++++++++++++++++++++++++------------------------ Cargo.toml | 2 +- src/sql/sql.rs | 9 +- 3 files changed, 323 insertions(+), 306 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b0c59a..790ecc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,46 +2,13 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "Omega" -version = "0.1.0" -dependencies = [ - "actix-web", - "aes-gcm", - "ansi_term", - "anyhow", - "base64", - "dashmap", - "dotenv", - "hex", - "hkdf", - "json", - "once_cell", - "rand 0.8.5", - "rand_core 0.6.4", - "reqwest", - "rustls", - "rustls-pemfile", - "sha2", - "sqlx", - "strum 0.27.2", - "strum_macros 0.27.2", - "thiserror 2.0.18", - "tokio", - "ttp-core", - "ttp-native", - "uuid", - "x448", - "zip", -] - [[package]] name = "actix-codec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-core", "futures-sink", @@ -54,9 +21,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.0" +version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f860ee6746d0c5b682147b2f7f8ef036d4f92fe518251a3a35ffa3650eafdf0e" +checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" dependencies = [ "actix-codec", "actix-rt", @@ -64,7 +31,7 @@ dependencies = [ "actix-tls", "actix-utils", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "brotli", "bytes", "bytestring", @@ -83,8 +50,8 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.9.2", - "sha1", + "rand 0.10.1", + "sha1 0.11.0", "smallvec", "tokio", "tokio-util", @@ -251,7 +218,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array", ] @@ -263,7 +230,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", ] [[package]] @@ -346,7 +313,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -538,9 +505,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.2" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -549,9 +516,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.39.1" +version = "0.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" dependencies = [ "cc", "cmake", @@ -579,9 +546,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" dependencies = [ "serde_core", ] @@ -595,6 +562,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "blocking" version = "1.6.2" @@ -649,9 +625,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "bytestring" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b4343b5f6617e7ad401ced8de3cc8b012e73a594347c307b90db3e9271289" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ "bytes", ] @@ -667,9 +643,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.59" +version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -677,12 +653,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -695,13 +665,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", ] @@ -739,6 +720,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "constant_time_eq" version = "0.3.1" @@ -800,6 +787,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc" version = "3.4.0" @@ -811,9 +807,9 @@ dependencies = [ [[package]] name = "crc-catalog" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" [[package]] name = "crc32fast" @@ -850,6 +846,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +dependencies = [ + "hybrid-array", +] + [[package]] name = "ctr" version = "0.9.2" @@ -875,9 +880,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deflate64" @@ -891,7 +896,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] @@ -959,12 +964,23 @@ version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", + "block-buffer 0.10.4", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.1", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -1298,6 +1314,7 @@ dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", + "rand_core 0.10.1", "wasip2", "wasip3", ] @@ -1381,9 +1398,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.1" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "hashlink" @@ -1433,7 +1450,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -1507,6 +1524,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "1.9.0" @@ -1530,15 +1556,14 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ "http 1.4.0", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", @@ -1670,9 +1695,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1686,12 +1711,12 @@ checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" [[package]] name = "indexmap" -version = "2.13.1" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", + "hashbrown 0.17.0", "serde", "serde_core", ] @@ -1749,27 +1774,32 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jni" -version = "0.21.1" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "cesu8", "cfg-if", "combine", - "jni-sys 0.3.1", + "jni-macros", + "jni-sys", "log", - "thiserror 1.0.69", + "simd_cesu8", + "thiserror", "walkdir", - "windows-sys 0.45.0", + "windows-link", ] [[package]] -name = "jni-sys" -version = "0.3.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" dependencies = [ - "jni-sys 0.4.1", + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn", ] [[package]] @@ -1803,9 +1833,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.94" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" dependencies = [ "cfg-if", "futures-util", @@ -1851,15 +1881,15 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libbz2-rs-sys" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" +checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" [[package]] name = "libc" -version = "0.2.184" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libm" @@ -1869,14 +1899,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" +checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.3", + "redox_syscall 0.7.4", ] [[package]] @@ -1955,7 +1985,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" dependencies = [ "crc", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1965,7 +1995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest", + "digest 0.10.7", ] [[package]] @@ -2039,7 +2069,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.5", + "rand 0.8.6", "smallvec", "zeroize", ] @@ -2095,6 +2125,39 @@ dependencies = [ "asn1-rs", ] +[[package]] +name = "omega" +version = "0.1.0" +dependencies = [ + "actix-web", + "aes-gcm", + "ansi_term", + "anyhow", + "base64", + "dashmap", + "dotenv", + "hex", + "hkdf", + "json", + "once_cell", + "rand 0.8.6", + "rand_core 0.6.4", + "reqwest", + "rustls", + "rustls-pemfile", + "sha2 0.10.9", + "sqlx", + "strum 0.27.2", + "strum_macros 0.27.2", + "thiserror", + "tokio", + "ttp-core", + "ttp-native", + "uuid", + "x448", + "zip", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -2148,7 +2211,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ - "digest", + "digest 0.10.7", "hmac", ] @@ -2223,9 +2286,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[package]] name = "plain" @@ -2270,7 +2333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -2338,7 +2401,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.3", - "thiserror 2.0.18", + "thiserror", "tokio", "tracing", "web-time", @@ -2354,13 +2417,13 @@ dependencies = [ "bytes", "getrandom 0.3.4", "lru-slab", - "rand 0.9.2", + "rand 0.9.4", "ring", "rustc-hash", "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.18", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2403,9 +2466,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", "rand_chacha 0.3.1", @@ -2414,14 +2477,25 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + [[package]] name = "rand_chacha" version = "0.3.1" @@ -2466,6 +2540,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "rcgen" version = "0.14.7" @@ -2475,7 +2555,7 @@ dependencies = [ "aws-lc-rs", "rustls-pki-types", "time", - "x509-parser 0.18.1", + "x509-parser", "yasna", ] @@ -2485,16 +2565,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] name = "redox_syscall" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" +checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", ] [[package]] @@ -2534,9 +2614,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" dependencies = [ "base64", "bytes", @@ -2590,8 +2670,8 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", - "digest", + "const-oid 0.9.6", + "digest 0.10.7", "num-bigint-dig", "num-integer", "num-traits", @@ -2648,7 +2728,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2657,9 +2737,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "aws-lc-rs", "log", @@ -2694,9 +2774,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "web-time", "zeroize", @@ -2704,9 +2784,9 @@ dependencies = [ [[package]] name = "rustls-platform-verifier" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ "core-foundation 0.10.1", "core-foundation-sys", @@ -2731,9 +2811,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.10" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", @@ -2783,7 +2863,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2868,8 +2948,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", ] [[package]] @@ -2879,8 +2970,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", - "digest", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.2", ] [[package]] @@ -2905,7 +3007,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", + "digest 0.10.7", "rand_core 0.6.4", ] @@ -2915,6 +3017,22 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + [[package]] name = "slab" version = "0.4.12" @@ -3019,9 +3137,9 @@ dependencies = [ "percent-encoding", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", - "thiserror 2.0.18", + "thiserror", "tracing", "url", ] @@ -3055,7 +3173,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -3072,11 +3190,11 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", "bytes", "crc", - "digest", + "digest 0.10.7", "dotenvy", "either", "futures-channel", @@ -3093,15 +3211,15 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.5", + "rand 0.8.6", "rsa", "serde", - "sha1", - "sha2", + "sha1 0.10.6", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror", "tracing", "whoami", ] @@ -3114,7 +3232,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.11.0", + "bitflags 2.11.1", "byteorder", "crc", "dotenvy", @@ -3131,14 +3249,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.5", + "rand 0.8.6", "serde", "serde_json", - "sha2", + "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror", "tracing", "whoami", ] @@ -3162,7 +3280,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.18", + "thiserror", "tracing", "url", ] @@ -3263,7 +3381,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3278,33 +3396,13 @@ dependencies = [ "libc", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.18", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "thiserror-impl", ] [[package]] @@ -3376,9 +3474,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.51.1" +version = "1.52.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f66bf9585cda4b724d3e78ab34b73fb2bbaba9011b9bfdf69dc836382ea13b8c" +checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" dependencies = [ "bytes", "libc", @@ -3446,7 +3544,7 @@ version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "bytes", "futures-util", "http 1.4.0", @@ -3511,11 +3609,12 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" dependencies = [ "base64", "byteorder", - "rand 0.8.5", + "rand 0.8.6", + "serde_json", "strum 0.28.0", "strum_macros 0.28.0", ] @@ -3523,12 +3622,12 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#2322bdced8183e970405d5017cd87ba805309a4c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" dependencies = [ "quinn", "rustls", "rustls-native-certs", - "thiserror 2.0.18", + "thiserror", "tokio", "ttp-core", "wtransport", @@ -3536,9 +3635,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-bidi" @@ -3585,7 +3684,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -3621,9 +3720,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.0" +version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3681,11 +3780,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.2+wasi-0.2.9" +version = "1.0.3+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.57.1", ] [[package]] @@ -3694,7 +3793,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -3705,9 +3804,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" dependencies = [ "cfg-if", "once_cell", @@ -3718,9 +3817,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.67" +version = "0.4.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" dependencies = [ "js-sys", "wasm-bindgen", @@ -3728,9 +3827,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3738,9 +3837,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" dependencies = [ "bumpalo", "proc-macro2", @@ -3751,9 +3850,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.117" +version = "0.2.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" dependencies = [ "unicode-ident", ] @@ -3786,7 +3885,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.11.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -3794,9 +3893,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.94" +version = "0.3.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" dependencies = [ "js-sys", "wasm-bindgen", @@ -3814,9 +3913,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" dependencies = [ "rustls-pki-types", ] @@ -3897,15 +3996,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3942,21 +4032,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -4005,12 +4080,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4029,12 +4098,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4053,12 +4116,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4089,12 +4146,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4113,12 +4164,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4137,12 +4182,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4161,12 +4200,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4194,6 +4227,12 @@ dependencies = [ "wit-bindgen-rust-macro", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wit-bindgen-core" version = "0.51.0" @@ -4243,7 +4282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.0", + "bitflags 2.11.1", "indexmap", "log", "serde", @@ -4281,9 +4320,9 @@ checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wtransport" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" +checksum = "ea4aacf790813ee1956751491800537f4e04af7557b7b370501ccbfbc85963e4" dependencies = [ "bytes", "pem", @@ -4292,26 +4331,26 @@ dependencies = [ "rustls", "rustls-native-certs", "rustls-pki-types", - "sha2", - "socket2 0.5.10", - "thiserror 2.0.18", + "sha2 0.11.0", + "socket2 0.6.3", + "thiserror", "time", "tokio", "tracing", "url", "wtransport-proto", - "x509-parser 0.17.0", + "x509-parser", ] [[package]] name = "wtransport-proto" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" +checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror 2.0.18", + "thiserror", "url", ] @@ -4326,23 +4365,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "x509-parser" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - [[package]] name = "x509-parser" version = "0.18.1" @@ -4357,7 +4379,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror 2.0.18", + "thiserror", "time", ] @@ -4507,7 +4529,7 @@ dependencies = [ "memchr", "pbkdf2", "ppmd-rust", - "sha1", + "sha1 0.10.6", "time", "zeroize", "zopfli", diff --git a/Cargo.toml b/Cargo.toml index 5e958ff..22ec471 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "Omega" +name = "omega" version = "0.1.0" edition = "2024" diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 8445e26..787d7d1 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -23,16 +23,11 @@ use crate::sql::{ static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); pub async fn connect() -> Result, sqlx::Error> { - let user = env::var("DB_USERNAME").expect("DB_USERNAME is not set"); - let passwd = env::var("DB_PASSWD").expect("DB_PASSWD is not set"); - let table = env::var("DB_TABLE").expect("DB_TABLE is not set"); + let url = env::var("DB_URL").expect("DB_URL is not set"); MySqlPoolOptions::new() .max_connections(200) - .connect(&format!( - "mysql://{}:{}@127.0.0.1:3306/{}", - user, passwd, table - )) + .connect(&url) .await } // Omega From 086c84e720e50a799681796f666be055fb8274bc Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 9 May 2026 14:52:08 +0200 Subject: [PATCH 117/220] [Add] Parrallel message sending --- Cargo.lock | 84 +++++++++++++---------------- src/transport/omikron_connection.rs | 25 ++++++++- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 790ecc9..21d6ccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -643,9 +643,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -972,9 +972,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", @@ -1362,9 +1362,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -1398,9 +1398,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" @@ -1543,7 +1543,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", + "h2 0.4.14", "http 1.4.0", "http-body", "httparse", @@ -1716,7 +1716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1756,16 +1756,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "itoa" version = "1.0.18" @@ -1833,9 +1823,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -1906,7 +1896,7 @@ dependencies = [ "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.4", + "redox_syscall 0.7.5", ] [[package]] @@ -2570,9 +2560,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" dependencies = [ "bitflags 2.11.1", ] @@ -2622,7 +2612,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.13", + "h2 0.4.14", "http 1.4.0", "http-body", "http-body-util", @@ -2960,7 +2950,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2982,7 +2972,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -3474,9 +3464,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3540,20 +3530,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ "bitflags 2.11.1", "bytes", "futures-util", "http 1.4.0", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -3609,7 +3599,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "base64", "byteorder", @@ -3622,7 +3612,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "quinn", "rustls", @@ -3804,9 +3794,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -3817,9 +3807,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.70" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", @@ -3827,9 +3817,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3837,9 +3827,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -3850,9 +3840,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -3893,9 +3883,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 47b42ef..d3453e0 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -21,7 +21,7 @@ use tokio::{ time::interval, }; use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Host, Receiver, Sender}; +use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; use x448::PublicKey; // ============================================================================ @@ -1112,7 +1112,28 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; + let mut host: Host = ttp_native::host( + port, + cert_pem, + key_pem, + Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(2_000), + write_timeout: Duration::from_millis(2_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + max_transient_recv_errors: 20, + transient_recv_backoff: Duration::from_millis(100), + receiver_queue_capacity: 1000, + }, + ) + .await?; log!("OmikronServer listening on port {}", port); while let Some((sender, mut receiver)) = host.next().await { From f23bd4cfef44e978156c21e94c09bddb8eab7613 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 9 May 2026 14:52:08 +0200 Subject: [PATCH 118/220] [Add] Parrallel message sending --- Cargo.lock | 84 +++++++++++++---------------- src/transport/omikron_connection.rs | 25 ++++++++- 2 files changed, 60 insertions(+), 49 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 790ecc9..21d6ccc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -643,9 +643,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.61" +version = "1.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" dependencies = [ "find-msvc-tools", "jobserver", @@ -972,9 +972,9 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", @@ -1362,9 +1362,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -1398,9 +1398,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" @@ -1543,7 +1543,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.13", + "h2 0.4.14", "http 1.4.0", "http-body", "httparse", @@ -1716,7 +1716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", "serde", "serde_core", ] @@ -1756,16 +1756,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "itoa" version = "1.0.18" @@ -1833,9 +1823,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" dependencies = [ "cfg-if", "futures-util", @@ -1906,7 +1896,7 @@ dependencies = [ "bitflags 2.11.1", "libc", "plain", - "redox_syscall 0.7.4", + "redox_syscall 0.7.5", ] [[package]] @@ -2570,9 +2560,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" +checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" dependencies = [ "bitflags 2.11.1", ] @@ -2622,7 +2612,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.13", + "h2 0.4.14", "http 1.4.0", "http-body", "http-body-util", @@ -2960,7 +2950,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2982,7 +2972,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -3474,9 +3464,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3540,20 +3530,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" dependencies = [ "bitflags 2.11.1", "bytes", "futures-util", "http 1.4.0", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -3609,7 +3599,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "base64", "byteorder", @@ -3622,7 +3612,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#ca183b262126bf80ae8afbc4ca929c10d72d873c" +source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "quinn", "rustls", @@ -3804,9 +3794,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" dependencies = [ "cfg-if", "once_cell", @@ -3817,9 +3807,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.70" +version = "0.4.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" +checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" dependencies = [ "js-sys", "wasm-bindgen", @@ -3827,9 +3817,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3837,9 +3827,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" dependencies = [ "bumpalo", "proc-macro2", @@ -3850,9 +3840,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.120" +version = "0.2.121" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" dependencies = [ "unicode-ident", ] @@ -3893,9 +3883,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.97" +version = "0.3.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" +checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 47b42ef..d3453e0 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -21,7 +21,7 @@ use tokio::{ time::interval, }; use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Host, Receiver, Sender}; +use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; use x448::PublicKey; // ============================================================================ @@ -1112,7 +1112,28 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; + let mut host: Host = ttp_native::host( + port, + cert_pem, + key_pem, + Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(2_000), + write_timeout: Duration::from_millis(2_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + max_transient_recv_errors: 20, + transient_recv_backoff: Duration::from_millis(100), + receiver_queue_capacity: 1000, + }, + ) + .await?; log!("OmikronServer listening on port {}", port); while let Some((sender, mut receiver)) = host.next().await { From 474d8c530871358ae0a7ec599f57dfbf7da433bd Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 16:18:53 +0200 Subject: [PATCH 119/220] [Add] Nix Flake --- flake.lock | 78 ++++++++++++++++++++++++ flake.nix | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6f56d6f --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1778716662, + "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1778443072, + "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1777168982, + "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "ttp": "ttp" + } + }, + "ttp": { + "flake": false, + "locked": { + "lastModified": 1778329030, + "narHash": "sha256-qEEPlOuGVco1g6lI/kfEvIZkJmBbjehuchGWPTywR10=", + "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", + "revCount": 115, + "type": "git", + "url": "https://git.methanium.net/Tensamin/TTP.git" + }, + "original": { + "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", + "type": "git", + "url": "https://git.methanium.net/Tensamin/TTP.git" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..900b774 --- /dev/null +++ b/flake.nix @@ -0,0 +1,171 @@ +{ + description = "Omega"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + ttp = { + url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=929cb9d3a6aebebe6365973f13062ac2a8e03af6"; + flake = false; + }; + }; + + outputs = inputs@{ self, nixpkgs, flake-parts, ttp, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + perSystem = { self', pkgs, ... }: { + packages = { + default = self'.packages.omega; + omega = pkgs.rustPlatform.buildRustPackage { + pname = "omega"; + version = "0.1.0"; + src = ./.; + cargoLock = { + lockFile = ./Cargo.lock; + allowBuiltinFetchGit = true; + }; + nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; + buildInputs = with pkgs; [ openssl libmysqlclient ]; + dontUseCmakeConfigure = true; + preConfigure = '' + if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then + cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json + fi + ''; + }; + }; + }; + + flake = { + nixosModules.default = { config, pkgs, lib, ... }: + let + cfg = config.services.omega; + defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default or (throw "omega: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); + in + { + options.services.omega = { + enable = lib.mkEnableOption "Omega, Tensamin's central server"; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/omega"; + description = "Directory where Omega stores its data and reads certificates from."; + }; + + apiCertFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL certificate file for the web/API server (e.g. ACME fullchain.pem). Copied to server_cert.pem at runtime."; + }; + + apiKeyFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL private key file for the web/API server (e.g. ACME key.pem). Converted to PKCS#8 and copied to server_key.pem at runtime."; + }; + + transportCertFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL certificate file for the TTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; + }; + + transportKeyFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL private key file for the TTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; + }; + + environmentFiles = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + description = '' + Environment files to load for the Omega service. + Must provide at least DB_URL, PRIVATE_KEY, and PUBLIC_KEY. + ''; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to open the firewall for ports used by Omega."; + }; + + package = lib.mkOption { + type = lib.types.package; + default = defaultPackage; + description = "The Omega package to use."; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.omega = { + isSystemUser = true; + group = "omega"; + home = cfg.dataDir; + createHome = true; + description = "Omega service user"; + }; + + users.groups.omega = { }; + + systemd.services.omega = { + description = "Tensamin Omega"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + Type = "simple"; + User = "omega"; + Group = "omega"; + WorkingDirectory = cfg.dataDir; + ExecStart = "${cfg.package}/bin/omega"; + Restart = "unless-stopped"; + RestartSec = "5"; + + EnvironmentFile = cfg.environmentFiles; + + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + + ExecStartPre = [ + ("+" + pkgs.writeShellScript "omega-setup-certs" '' + mkdir -p ${cfg.dataDir}/certs + cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.apiKeyFile} \ + -out ${cfg.dataDir}/certs/server_key.pem + cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.transportKeyFile} \ + -out ${cfg.dataDir}/certs/transport_key.pem + chown -R omega:omega ${cfg.dataDir} + '') + ]; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + ReadWritePaths = cfg.dataDir; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ 9187 9188 ]; + allowedUDPPorts = [ 9187 9188 ]; + }; + }; + }; + }; + }; +} From d644aaaa6ddd9c775e6c4edf3c116eb9a97b6ac6 Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 16:18:53 +0200 Subject: [PATCH 120/220] [Add] Nix Flake --- flake.lock | 78 ++++++++++++++++++++++++ flake.nix | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 249 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6f56d6f --- /dev/null +++ b/flake.lock @@ -0,0 +1,78 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1778716662, + "narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1778443072, + "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1777168982, + "narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs", + "ttp": "ttp" + } + }, + "ttp": { + "flake": false, + "locked": { + "lastModified": 1778329030, + "narHash": "sha256-qEEPlOuGVco1g6lI/kfEvIZkJmBbjehuchGWPTywR10=", + "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", + "revCount": 115, + "type": "git", + "url": "https://git.methanium.net/Tensamin/TTP.git" + }, + "original": { + "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", + "type": "git", + "url": "https://git.methanium.net/Tensamin/TTP.git" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..900b774 --- /dev/null +++ b/flake.nix @@ -0,0 +1,171 @@ +{ + description = "Omega"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + flake-parts.url = "github:hercules-ci/flake-parts"; + ttp = { + url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=929cb9d3a6aebebe6365973f13062ac2a8e03af6"; + flake = false; + }; + }; + + outputs = inputs@{ self, nixpkgs, flake-parts, ttp, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + + perSystem = { self', pkgs, ... }: { + packages = { + default = self'.packages.omega; + omega = pkgs.rustPlatform.buildRustPackage { + pname = "omega"; + version = "0.1.0"; + src = ./.; + cargoLock = { + lockFile = ./Cargo.lock; + allowBuiltinFetchGit = true; + }; + nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; + buildInputs = with pkgs; [ openssl libmysqlclient ]; + dontUseCmakeConfigure = true; + preConfigure = '' + if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then + cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json + fi + ''; + }; + }; + }; + + flake = { + nixosModules.default = { config, pkgs, lib, ... }: + let + cfg = config.services.omega; + defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default or (throw "omega: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); + in + { + options.services.omega = { + enable = lib.mkEnableOption "Omega, Tensamin's central server"; + + dataDir = lib.mkOption { + type = lib.types.str; + default = "/var/lib/omega"; + description = "Directory where Omega stores its data and reads certificates from."; + }; + + apiCertFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL certificate file for the web/API server (e.g. ACME fullchain.pem). Copied to server_cert.pem at runtime."; + }; + + apiKeyFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL private key file for the web/API server (e.g. ACME key.pem). Converted to PKCS#8 and copied to server_key.pem at runtime."; + }; + + transportCertFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL certificate file for the TTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; + }; + + transportKeyFile = lib.mkOption { + type = lib.types.path; + description = "Path to the SSL private key file for the TTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; + }; + + environmentFiles = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + description = '' + Environment files to load for the Omega service. + Must provide at least DB_URL, PRIVATE_KEY, and PUBLIC_KEY. + ''; + }; + + openFirewall = lib.mkOption { + type = lib.types.bool; + default = true; + description = "Whether to open the firewall for ports used by Omega."; + }; + + package = lib.mkOption { + type = lib.types.package; + default = defaultPackage; + description = "The Omega package to use."; + }; + }; + + config = lib.mkIf cfg.enable { + users.users.omega = { + isSystemUser = true; + group = "omega"; + home = cfg.dataDir; + createHome = true; + description = "Omega service user"; + }; + + users.groups.omega = { }; + + systemd.services.omega = { + description = "Tensamin Omega"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + + serviceConfig = { + Type = "simple"; + User = "omega"; + Group = "omega"; + WorkingDirectory = cfg.dataDir; + ExecStart = "${cfg.package}/bin/omega"; + Restart = "unless-stopped"; + RestartSec = "5"; + + EnvironmentFile = cfg.environmentFiles; + + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; + + ExecStartPre = [ + ("+" + pkgs.writeShellScript "omega-setup-certs" '' + mkdir -p ${cfg.dataDir}/certs + cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.apiKeyFile} \ + -out ${cfg.dataDir}/certs/server_key.pem + cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.transportKeyFile} \ + -out ${cfg.dataDir}/certs/transport_key.pem + chown -R omega:omega ${cfg.dataDir} + '') + ]; + + ProtectSystem = "strict"; + ProtectHome = true; + PrivateTmp = true; + NoNewPrivileges = true; + ReadWritePaths = cfg.dataDir; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + }; + }; + + networking.firewall = lib.mkIf cfg.openFirewall { + allowedTCPPorts = [ 9187 9188 ]; + allowedUDPPorts = [ 9187 9188 ]; + }; + }; + }; + }; + }; +} From 6d96de92d74b6f37c0ef254fcf1aba8e74eec46e Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 16:43:39 +0200 Subject: [PATCH 121/220] [Fix] Nix Flake --- flake.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 900b774..9c34223 100644 --- a/flake.nix +++ b/flake.nix @@ -122,8 +122,8 @@ Group = "omega"; WorkingDirectory = cfg.dataDir; ExecStart = "${cfg.package}/bin/omega"; - Restart = "unless-stopped"; - RestartSec = "5"; + Restart = "always"; + RestartSec = "5s"; EnvironmentFile = cfg.environmentFiles; From b02795b80c6ea472bfd8aaccd684656dab2bf3d9 Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 16:43:39 +0200 Subject: [PATCH 122/220] [Fix] Nix Flake --- flake.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 900b774..9c34223 100644 --- a/flake.nix +++ b/flake.nix @@ -122,8 +122,8 @@ Group = "omega"; WorkingDirectory = cfg.dataDir; ExecStart = "${cfg.package}/bin/omega"; - Restart = "unless-stopped"; - RestartSec = "5"; + Restart = "always"; + RestartSec = "5s"; EnvironmentFile = cfg.environmentFiles; From fe0250218dd5858822270935a5d672a1d1368327 Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 17:34:10 +0200 Subject: [PATCH 123/220] [Fix] use working directory instead of executable directory --- src/util/file_util.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/util/file_util.rs b/src/util/file_util.rs index b108810..f1c51db 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -163,9 +163,8 @@ pub fn get_children(path: &str) -> Vec { } pub fn get_directory() -> String { - let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); - exe.parent() - .unwrap_or(Path::new(".")) + std::env::current_dir() + .unwrap_or_else(|_| PathBuf::from(".")) .to_string_lossy() .to_string() } From 8451b64bea8c904bfca0682be27f66adc97c2d3b Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 17:34:10 +0200 Subject: [PATCH 124/220] [Fix] use working directory instead of executable directory --- src/util/file_util.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/util/file_util.rs b/src/util/file_util.rs index b108810..f1c51db 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -163,9 +163,8 @@ pub fn get_children(path: &str) -> Vec { } pub fn get_directory() -> String { - let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); - exe.parent() - .unwrap_or(Path::new(".")) + std::env::current_dir() + .unwrap_or_else(|_| PathBuf::from(".")) .to_string_lossy() .to_string() } From fa7c0606244cd0d93a8faead5b81e54801a5e601 Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 17:44:40 +0200 Subject: [PATCH 125/220] [Add] port options in nix flake --- flake.nix | 21 +++++++++++++++++++-- src/main.rs | 14 ++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 9c34223..55f73e0 100644 --- a/flake.nix +++ b/flake.nix @@ -87,6 +87,18 @@ ''; }; + apiPort = lib.mkOption { + type = lib.types.port; + default = 9188; + description = "Port for the web/API server."; + }; + + transportPort = lib.mkOption { + type = lib.types.port; + default = 9187; + description = "Port for the TTP/QUIC transport server."; + }; + openFirewall = lib.mkOption { type = lib.types.bool; default = true; @@ -125,6 +137,11 @@ Restart = "always"; RestartSec = "5s"; + Environment = [ + "API_PORT=${toString cfg.apiPort}" + "OMIKRON_PORT=${toString cfg.transportPort}" + ]; + EnvironmentFile = cfg.environmentFiles; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; @@ -161,8 +178,8 @@ }; networking.firewall = lib.mkIf cfg.openFirewall { - allowedTCPPorts = [ 9187 9188 ]; - allowedUDPPorts = [ 9187 9188 ]; + allowedTCPPorts = [ cfg.transportPort cfg.apiPort ]; + allowedUDPPorts = [ cfg.transportPort cfg.apiPort ]; }; }; }; diff --git a/src/main.rs b/src/main.rs index 8143c9f..7d5cfcc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,8 +35,13 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); + let omikron_port: u16 = env::var("OMIKRON_PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(9187); + tokio::spawn(async move { - match omikron_connection::start(9187).await { + match omikron_connection::start(omikron_port).await { Err(e) => log_err!(0, PrintType::General, "{:?}", e), _ => {} } @@ -59,7 +64,12 @@ async fn main() { log!(" Users"); } - let _ = server::server::start(9188).await; + let api_port: u16 = env::var("API_PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(9188); + + let _ = server::server::start(api_port).await; tokio::signal::ctrl_c().await.unwrap(); } From 7e9e834326d6c724a2780ee5af5f2eab265f8249 Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 17:44:40 +0200 Subject: [PATCH 126/220] [Add] port options in nix flake --- flake.nix | 21 +++++++++++++++++++-- src/main.rs | 14 ++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 9c34223..55f73e0 100644 --- a/flake.nix +++ b/flake.nix @@ -87,6 +87,18 @@ ''; }; + apiPort = lib.mkOption { + type = lib.types.port; + default = 9188; + description = "Port for the web/API server."; + }; + + transportPort = lib.mkOption { + type = lib.types.port; + default = 9187; + description = "Port for the TTP/QUIC transport server."; + }; + openFirewall = lib.mkOption { type = lib.types.bool; default = true; @@ -125,6 +137,11 @@ Restart = "always"; RestartSec = "5s"; + Environment = [ + "API_PORT=${toString cfg.apiPort}" + "OMIKRON_PORT=${toString cfg.transportPort}" + ]; + EnvironmentFile = cfg.environmentFiles; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; @@ -161,8 +178,8 @@ }; networking.firewall = lib.mkIf cfg.openFirewall { - allowedTCPPorts = [ 9187 9188 ]; - allowedUDPPorts = [ 9187 9188 ]; + allowedTCPPorts = [ cfg.transportPort cfg.apiPort ]; + allowedUDPPorts = [ cfg.transportPort cfg.apiPort ]; }; }; }; diff --git a/src/main.rs b/src/main.rs index 8143c9f..7d5cfcc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -35,8 +35,13 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); + let omikron_port: u16 = env::var("OMIKRON_PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(9187); + tokio::spawn(async move { - match omikron_connection::start(9187).await { + match omikron_connection::start(omikron_port).await { Err(e) => log_err!(0, PrintType::General, "{:?}", e), _ => {} } @@ -59,7 +64,12 @@ async fn main() { log!(" Users"); } - let _ = server::server::start(9188).await; + let api_port: u16 = env::var("API_PORT") + .ok() + .and_then(|s| s.parse().ok()) + .unwrap_or(9188); + + let _ = server::server::start(api_port).await; tokio::signal::ctrl_c().await.unwrap(); } From db63f68bee82c4aff54b3c1cee4f4950218bcc3d Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 19:02:12 +0200 Subject: [PATCH 127/220] [Add] ttpBind --- Cargo.lock | 4 ++-- flake.lock | 11 ++++++----- flake.nix | 16 +++++++++++++++- src/server/server.rs | 3 ++- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21d6ccc..aa6be62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3599,7 +3599,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "base64", "byteorder", @@ -3612,7 +3612,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "quinn", "rustls", diff --git a/flake.lock b/flake.lock index 6f56d6f..e742243 100644 --- a/flake.lock +++ b/flake.lock @@ -59,15 +59,16 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1778329030, - "narHash": "sha256-qEEPlOuGVco1g6lI/kfEvIZkJmBbjehuchGWPTywR10=", - "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", - "revCount": 115, + "lastModified": 1778948017, + "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", + "ref": "refs/heads/main", + "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", + "revCount": 117, "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" }, "original": { - "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", + "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" } diff --git a/flake.nix b/flake.nix index 55f73e0..29f2373 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; ttp = { - url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=929cb9d3a6aebebe6365973f13062ac2a8e03af6"; + url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=7e5d1953df8592a1feba0f390d205d0ef61a3119"; flake = false; }; }; @@ -99,6 +99,18 @@ description = "Port for the TTP/QUIC transport server."; }; + ttpBind = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "IP address to bind the TTP/QUIC transport server to."; + }; + + bindAddress = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "IP address to bind the HTTP/API server to."; + }; + openFirewall = lib.mkOption { type = lib.types.bool; default = true; @@ -140,6 +152,8 @@ Environment = [ "API_PORT=${toString cfg.apiPort}" "OMIKRON_PORT=${toString cfg.transportPort}" + "TTP_BIND=${cfg.ttpBind}" + "BIND_ADDRESS=${cfg.bindAddress}" ]; EnvironmentFile = cfg.environmentFiles; diff --git a/src/server/server.rs b/src/server/server.rs index 1fcfe04..3c7fc05 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -30,7 +30,8 @@ pub async fn start(port: u16) -> anyhow::Result<()> { config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - let addr = format!("0.0.0.0:{port}"); + let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); + let addr = format!("{}:{}", bind_addr, port); log!(" Server on {}", addr); HttpServer::new(move || { From a3f9e0ea3af65c17ada6e9e9b051e17ea96c2e0f Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 16 May 2026 19:02:12 +0200 Subject: [PATCH 128/220] [Add] ttpBind --- Cargo.lock | 4 ++-- flake.lock | 11 ++++++----- flake.nix | 16 +++++++++++++++- src/server/server.rs | 3 ++- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21d6ccc..aa6be62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3599,7 +3599,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "base64", "byteorder", @@ -3612,7 +3612,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "quinn", "rustls", diff --git a/flake.lock b/flake.lock index 6f56d6f..e742243 100644 --- a/flake.lock +++ b/flake.lock @@ -59,15 +59,16 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1778329030, - "narHash": "sha256-qEEPlOuGVco1g6lI/kfEvIZkJmBbjehuchGWPTywR10=", - "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", - "revCount": 115, + "lastModified": 1778948017, + "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", + "ref": "refs/heads/main", + "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", + "revCount": 117, "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" }, "original": { - "rev": "929cb9d3a6aebebe6365973f13062ac2a8e03af6", + "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" } diff --git a/flake.nix b/flake.nix index 55f73e0..29f2373 100644 --- a/flake.nix +++ b/flake.nix @@ -5,7 +5,7 @@ nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; ttp = { - url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=929cb9d3a6aebebe6365973f13062ac2a8e03af6"; + url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=7e5d1953df8592a1feba0f390d205d0ef61a3119"; flake = false; }; }; @@ -99,6 +99,18 @@ description = "Port for the TTP/QUIC transport server."; }; + ttpBind = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "IP address to bind the TTP/QUIC transport server to."; + }; + + bindAddress = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "IP address to bind the HTTP/API server to."; + }; + openFirewall = lib.mkOption { type = lib.types.bool; default = true; @@ -140,6 +152,8 @@ Environment = [ "API_PORT=${toString cfg.apiPort}" "OMIKRON_PORT=${toString cfg.transportPort}" + "TTP_BIND=${cfg.ttpBind}" + "BIND_ADDRESS=${cfg.bindAddress}" ]; EnvironmentFile = cfg.environmentFiles; diff --git a/src/server/server.rs b/src/server/server.rs index 1fcfe04..3c7fc05 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -30,7 +30,8 @@ pub async fn start(port: u16) -> anyhow::Result<()> { config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - let addr = format!("0.0.0.0:{port}"); + let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); + let addr = format!("{}:{}", bind_addr, port); log!(" Server on {}", addr); HttpServer::new(move || { From e98aeccbedde9083a4734735ecb49faccddad172 Mon Sep 17 00:00:00 2001 From: Alois Date: Sun, 17 May 2026 13:44:19 +0200 Subject: [PATCH 129/220] [Add] nix dev shell --- flake.lock | 28 +++++++++++++++++++++++---- flake.nix | 57 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/flake.lock b/flake.lock index e742243..fb5bf79 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778443072, - "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", "type": "github" }, "original": { @@ -53,15 +53,35 @@ "inputs": { "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", "ttp": "ttp" } }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778987862, + "narHash": "sha256-V3qGt9P1eJP/r/1ONablphfGiH0RP4agQhrRANpDYx8=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "6f44d8874ac29806c8d5cae42bf8e19ebb5ce0d3", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, "ttp": { "flake": false, "locked": { "lastModified": 1778948017, "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", - "ref": "refs/heads/main", "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "revCount": 117, "type": "git", diff --git a/flake.nix b/flake.nix index 29f2373..97b1bcc 100644 --- a/flake.nix +++ b/flake.nix @@ -4,13 +4,17 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; ttp = { url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=7e5d1953df8592a1feba0f390d205d0ef61a3119"; flake = false; }; }; - outputs = inputs@{ self, nixpkgs, flake-parts, ttp, ... }: + outputs = inputs@{ self, nixpkgs, flake-parts, rust-overlay, ttp, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" @@ -19,28 +23,43 @@ "aarch64-darwin" ]; - perSystem = { self', pkgs, ... }: { - packages = { - default = self'.packages.omega; - omega = pkgs.rustPlatform.buildRustPackage { - pname = "omega"; - version = "0.1.0"; - src = ./.; - cargoLock = { - lockFile = ./Cargo.lock; - allowBuiltinFetchGit = true; + perSystem = { self', pkgs, system, ... }: + let + rustPkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + rustToolchain = rustPkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; + }; + in + { + packages = { + default = self'.packages.omega; + omega = pkgs.rustPlatform.buildRustPackage { + pname = "omega"; + version = "0.1.0"; + src = ./.; + cargoLock = { + lockFile = ./Cargo.lock; + allowBuiltinFetchGit = true; + }; + nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; + buildInputs = with pkgs; [ openssl libmysqlclient ]; + dontUseCmakeConfigure = true; + preConfigure = '' + if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then + cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json + fi + ''; }; - nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; + }; + + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ rustToolchain git cmake perl pkg-config ]; buildInputs = with pkgs; [ openssl libmysqlclient ]; - dontUseCmakeConfigure = true; - preConfigure = '' - if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then - cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json - fi - ''; }; }; - }; flake = { nixosModules.default = { config, pkgs, lib, ... }: From 046654cad7e995eabefd1ff6a6dfce8291da971c Mon Sep 17 00:00:00 2001 From: Alois Date: Sun, 17 May 2026 13:44:19 +0200 Subject: [PATCH 130/220] [Add] nix dev shell --- flake.lock | 28 +++++++++++++++++++++++---- flake.nix | 57 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 62 insertions(+), 23 deletions(-) diff --git a/flake.lock b/flake.lock index e742243..fb5bf79 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778443072, - "narHash": "sha256-zi7/fsqM/kFdNuED//4WOCUtezGtKKqRNORjMvfwjnA=", + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "owner": "nixos", "repo": "nixpkgs", - "rev": "da5ad661ba4e5ef59ba743f0d112cbc30e474f32", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", "type": "github" }, "original": { @@ -53,15 +53,35 @@ "inputs": { "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay", "ttp": "ttp" } }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1778987862, + "narHash": "sha256-V3qGt9P1eJP/r/1ONablphfGiH0RP4agQhrRANpDYx8=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "6f44d8874ac29806c8d5cae42bf8e19ebb5ce0d3", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + }, "ttp": { "flake": false, "locked": { "lastModified": 1778948017, "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", - "ref": "refs/heads/main", "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "revCount": 117, "type": "git", diff --git a/flake.nix b/flake.nix index 29f2373..97b1bcc 100644 --- a/flake.nix +++ b/flake.nix @@ -4,13 +4,17 @@ inputs = { nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; flake-parts.url = "github:hercules-ci/flake-parts"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; ttp = { url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=7e5d1953df8592a1feba0f390d205d0ef61a3119"; flake = false; }; }; - outputs = inputs@{ self, nixpkgs, flake-parts, ttp, ... }: + outputs = inputs@{ self, nixpkgs, flake-parts, rust-overlay, ttp, ... }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" @@ -19,28 +23,43 @@ "aarch64-darwin" ]; - perSystem = { self', pkgs, ... }: { - packages = { - default = self'.packages.omega; - omega = pkgs.rustPlatform.buildRustPackage { - pname = "omega"; - version = "0.1.0"; - src = ./.; - cargoLock = { - lockFile = ./Cargo.lock; - allowBuiltinFetchGit = true; + perSystem = { self', pkgs, system, ... }: + let + rustPkgs = import nixpkgs { + inherit system; + overlays = [ (import rust-overlay) ]; + }; + rustToolchain = rustPkgs.rust-bin.stable.latest.default.override { + extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; + }; + in + { + packages = { + default = self'.packages.omega; + omega = pkgs.rustPlatform.buildRustPackage { + pname = "omega"; + version = "0.1.0"; + src = ./.; + cargoLock = { + lockFile = ./Cargo.lock; + allowBuiltinFetchGit = true; + }; + nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; + buildInputs = with pkgs; [ openssl libmysqlclient ]; + dontUseCmakeConfigure = true; + preConfigure = '' + if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then + cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json + fi + ''; }; - nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; + }; + + devShells.default = pkgs.mkShell { + nativeBuildInputs = with pkgs; [ rustToolchain git cmake perl pkg-config ]; buildInputs = with pkgs; [ openssl libmysqlclient ]; - dontUseCmakeConfigure = true; - preConfigure = '' - if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then - cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json - fi - ''; }; }; - }; flake = { nixosModules.default = { config, pkgs, lib, ... }: From 8f78df03f765c54d1135ee964804a99c53d14d17 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 17 May 2026 21:34:55 +0200 Subject: [PATCH 131/220] [Add] basic Tauri --- Cargo.lock | 2 - Cargo.toml | 4 +- src/main.rs | 9 ++ src/notifications/mod.rs | 1 + src/notifications/tauri.rs | 160 ++++++++++++++++++++++++++++ src/transport/omikron_connection.rs | 93 ++++++++++------ src/transport/omikron_manager.rs | 10 ++ src/util/logger.rs | 37 +++---- 8 files changed, 261 insertions(+), 55 deletions(-) create mode 100644 src/notifications/mod.rs create mode 100644 src/notifications/tauri.rs diff --git a/Cargo.lock b/Cargo.lock index 21d6ccc..b34b40b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3599,7 +3599,6 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "base64", "byteorder", @@ -3612,7 +3611,6 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "quinn", "rustls", diff --git a/Cargo.toml b/Cargo.toml index 22ec471..c6c6bf4 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } -ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } +ttp-core = { path = "../ttp/core" } +ttp-native = { path = "../ttp/native" } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" diff --git a/src/main.rs b/src/main.rs index 8143c9f..a6b127a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ +mod notifications; mod server; mod sql; mod transport; mod util; +use crate::notifications::tauri; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::transport::omikron_connection; @@ -42,6 +44,13 @@ async fn main() { } }); + tokio::spawn(async move { + match tauri::start(9189).await { + Err(e) => log_err!(0, PrintType::General, "{:?}", e), + _ => {} + } + }); + log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { diff --git a/src/notifications/mod.rs b/src/notifications/mod.rs new file mode 100644 index 0000000..62411f7 --- /dev/null +++ b/src/notifications/mod.rs @@ -0,0 +1 @@ +pub mod tauri; diff --git a/src/notifications/tauri.rs b/src/notifications/tauri.rs new file mode 100644 index 0000000..c57c26d --- /dev/null +++ b/src/notifications/tauri.rs @@ -0,0 +1,160 @@ +use crate::log; +use crate::util::file_util::load_file_vec; +use crate::util::logger::PrintType; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::futures; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; + +pub struct TauriConnection { + pub user_id: i64, + pub sender: Arc, +} + +static TAURI_CONNECTIONS: Lazy>>> = Lazy::new(DashMap::new); + +pub async fn start(port: u16) -> Result<(), Box> { + let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); + let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); + + let mut host: Host = ttp_native::host( + port, + cert_pem, + key_pem, + Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(2_000), + write_timeout: Duration::from_millis(2_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + max_transient_recv_errors: 20, + transient_recv_backoff: Duration::from_millis(100), + receiver_queue_capacity: 1000, + }, + ) + .await?; + + log!("TauriServer listening on port {}", port); + + while let Some((sender, mut receiver)) = host.next().await { + tokio::spawn(async move { + handle_connection(sender, &mut receiver).await; + }); + } + + Ok(()) +} + +async fn handle_connection(sender: Sender, receiver: &mut Receiver) { + let mut current_user_id = 0; + let sender = Arc::new(sender); + + while let Ok(cv) = receiver.receive().await { + match cv.get_type() { + CommunicationType::tauri_identification => { + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + if user_id != 0 { + current_user_id = user_id; + let conn = Arc::new(TauriConnection { + user_id, + sender: sender.clone(), + }); + + TAURI_CONNECTIONS + .entry(user_id) + .and_modify(|conns| { + conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); + conns.push(conn.clone()); + }) + .or_insert_with(|| vec![conn]); + + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + if let Err(e) = sender.send(&response).await { + log!( + PrintType::General, + "Failed to send tauri success response: {}", + e + ); + } else { + log!(user_id, PrintType::Client, "Tauri device registered"); + } + } + } + CommunicationType::ping => { + let response = + CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + if let Err(_) = sender.send(&response).await { + break; + } + } + _ => {} + } + } + + // Cleanup + if current_user_id != 0 { + if let Some(mut conns) = TAURI_CONNECTIONS.get_mut(¤t_user_id) { + conns.retain(|c| !Arc::ptr_eq(c.sender.handle(), sender.handle())); + } + } +} + +pub async fn send_notification(user_id: i64, sender_id: i64) { + let conns_opt = { + let entry = TAURI_CONNECTIONS.get(&user_id); + entry.map(|e| e.clone()) + }; + + if let Some(conns) = conns_opt { + let cv = CommunicationValue::new(CommunicationType::push_notification) + .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + + let mut remove_needed = false; + for conn in conns.iter() { + if conn.sender.send(&cv).await.is_err() { + remove_needed = true; + } + } + + if remove_needed { + if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { + conns_mut.retain(|conn| conn.sender.is_open()); + } + } + } +} + +pub async fn remove_notification(user_id: i64, sender_id: i64) { + let conns_opt = { + let entry = TAURI_CONNECTIONS.get(&user_id); + entry.map(|e| e.clone()) + }; + + if let Some(conns) = conns_opt { + let cv = CommunicationValue::new(CommunicationType::read_notification) + .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + + let mut remove_needed = false; + for conn in conns.iter() { + if conn.sender.send(&cv).await.is_err() { + remove_needed = true; + } + } + + if remove_needed { + if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { + conns_mut.retain(|conn| conn.sender.is_open()); + } + } + } +} diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index d3453e0..8205775 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -966,8 +966,8 @@ impl OmikronConnection { cv: CommunicationValue, ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; - if let Ok(notifications) = sql::get_notifications(user_id).await { - let json_array: Vec = notifications + let response_array = match sql::get_notifications(user_id).await { + Ok(notifications) => notifications .into_iter() .map(|(sender, amount)| { DataValue::Container(vec![ @@ -975,59 +975,90 @@ impl OmikronConnection { (DataTypes::amount, DataValue::Number(amount)), ]) }) - .collect(); + .collect(), + Err(e) => { + log!(PrintType::General, "SQL get_notifications error: {}", e); + vec![] + } + }; - let response = CommunicationValue::new(CommunicationType::get_notifications) - .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(json_array)); - self.send(&response).await - } else { - Ok(()) - } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_data(DataTypes::notifications, DataValue::Array(response_array)); + self.send(&response).await } async fn handle_read_notification( self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; + let receiver_id = match cv.get_sender() { + s if s > 0 => s as i64, + _ => match cv.get_data(DataTypes::receiver_id).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + if let Some(other_id) = cv .get_data(DataTypes::sender_id) .as_number() .map(|n| n as i64) { - if sql::read_notification(user_id, other_id).await.is_ok() { + if let Err(e) = sql::read_notification(receiver_id, other_id).await { + log!(PrintType::General, "SQL read_notification error: {}", e); + } else { let response = CommunicationValue::new(CommunicationType::read_notification) .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) + let _ = self.send(&response).await; + + // Sync with Tauri + crate::notifications::tauri::remove_notification(receiver_id, other_id).await; + + // Sync with other Omikron clients + let sync_cv = CommunicationValue::new(CommunicationType::read_notification) + .with_receiver(receiver_id as u64) + .add_data(DataTypes::sender_id, DataValue::Number(other_id)); + crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; } - } else { - Ok(()) } + Ok(()) } async fn handle_push_notification( self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Some(other_id) = cv - .get_data(DataTypes::sender_id) - .as_number() - .map(|n| n as i64) - { - if sql::add_notification(user_id, other_id).await.is_ok() { - let response = CommunicationValue::new(CommunicationType::push_notification) - .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) - } + let receiver_id = match cv.get_receiver() { + r if r > 0 => r as i64, + _ => match cv.get_data(DataTypes::receiver_id).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + + let sender_id = match cv.get_data(DataTypes::sender_id).as_number() { + Some(id) => id as i64, + None => cv.get_sender() as i64, + }; + + if let Err(e) = sql::add_notification(receiver_id, sender_id).await { + log!(PrintType::General, "SQL add_notification error: {}", e); } else { - Ok(()) + let response = + CommunicationValue::new(CommunicationType::push_notification).with_id(cv.get_id()); + let _ = self.send(&response).await; + + // Sync with Tauri + crate::notifications::tauri::send_notification(receiver_id, sender_id).await; + + // Sync with other Omikron clients + let push_cv = CommunicationValue::new(CommunicationType::push_notification) + .with_receiver(receiver_id as u64) + .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; } + Ok(()) } async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index fbee4a9..70c73cd 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -3,6 +3,7 @@ use dashmap::DashMap; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; use std::sync::Arc; +use ttp_core::CommunicationValue; pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); @@ -24,6 +25,7 @@ pub async fn add_omikron(conn: Arc) { pub async fn remove_omikron(omikron_id: i64) { OMIKRON_CONNECTIONS.remove(&omikron_id); } + pub async fn get_random_omikron() -> Result, ()> { let mut rng = rand::thread_rng(); @@ -37,3 +39,11 @@ pub async fn get_random_omikron() -> Result, ()> { Err(()) } + +pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) { + if let Some(user_conn) = crate::sql::user_online_tracker::get_user_status(user_id) { + if let Some(omikron_conn) = OMIKRON_CONNECTIONS.get(&user_conn.omikron_id) { + let _ = omikron_conn.value().clone().send_message(cv).await; + } + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs index e2e6c92..7fa3947 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -9,7 +9,6 @@ use std::{ }; use ansi_term::Color; -use json::JsonValue; use ttp_core::{CommunicationValue, DataTypes, DataValue}; static LOGGER: OnceLock> = OnceLock::new(); @@ -119,6 +118,16 @@ pub fn log_internal( #[macro_export] macro_rules! log { + // sender + actor + ($sender:expr, $kind:path, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) + }; + + // actor only + ($kind:path, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) + }; + // plain ($($arg:tt)*) => { $crate::util::logger::log_internal( @@ -129,27 +138,17 @@ macro_rules! log { format!($($arg)*) ) }; - - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) - }; } #[macro_export] macro_rules! log_in { // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { + ($sender:expr, $kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) }; // actor only - ($kind:expr, $($arg:tt)*) => { + ($kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) }; @@ -167,14 +166,13 @@ macro_rules! log_in { #[macro_export] macro_rules! log_out { - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { + ($sender:expr, $kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) }; // actor only - ($kind:expr, $($arg:tt)*) => { + ($kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) }; @@ -192,14 +190,13 @@ macro_rules! log_out { #[macro_export] macro_rules! log_err { - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { + ($sender:expr, $kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) }; // actor only - ($kind:expr, $($arg:tt)*) => { + ($kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) }; From 9904e54914d45a376f4fc7a3f1dcc04d6ddd57e2 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 17 May 2026 21:34:55 +0200 Subject: [PATCH 132/220] [Add] basic Tauri --- Cargo.lock | 2 - Cargo.toml | 4 +- src/main.rs | 9 ++ src/notifications/mod.rs | 1 + src/notifications/tauri.rs | 160 ++++++++++++++++++++++++++++ src/transport/omikron_connection.rs | 93 ++++++++++------ src/transport/omikron_manager.rs | 10 ++ src/util/logger.rs | 37 +++---- 8 files changed, 261 insertions(+), 55 deletions(-) create mode 100644 src/notifications/mod.rs create mode 100644 src/notifications/tauri.rs diff --git a/Cargo.lock b/Cargo.lock index 21d6ccc..b34b40b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3599,7 +3599,6 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "base64", "byteorder", @@ -3612,7 +3611,6 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#929cb9d3a6aebebe6365973f13062ac2a8e03af6" dependencies = [ "quinn", "rustls", diff --git a/Cargo.toml b/Cargo.toml index 22ec471..c6c6bf4 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } -ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } +ttp-core = { path = "../ttp/core" } +ttp-native = { path = "../ttp/native" } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" diff --git a/src/main.rs b/src/main.rs index 8143c9f..a6b127a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ +mod notifications; mod server; mod sql; mod transport; mod util; +use crate::notifications::tauri; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::transport::omikron_connection; @@ -42,6 +44,13 @@ async fn main() { } }); + tokio::spawn(async move { + match tauri::start(9189).await { + Err(e) => log_err!(0, PrintType::General, "{:?}", e), + _ => {} + } + }); + log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { diff --git a/src/notifications/mod.rs b/src/notifications/mod.rs new file mode 100644 index 0000000..62411f7 --- /dev/null +++ b/src/notifications/mod.rs @@ -0,0 +1 @@ +pub mod tauri; diff --git a/src/notifications/tauri.rs b/src/notifications/tauri.rs new file mode 100644 index 0000000..c57c26d --- /dev/null +++ b/src/notifications/tauri.rs @@ -0,0 +1,160 @@ +use crate::log; +use crate::util::file_util::load_file_vec; +use crate::util::logger::PrintType; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::futures; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; + +pub struct TauriConnection { + pub user_id: i64, + pub sender: Arc, +} + +static TAURI_CONNECTIONS: Lazy>>> = Lazy::new(DashMap::new); + +pub async fn start(port: u16) -> Result<(), Box> { + let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); + let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); + + let mut host: Host = ttp_native::host( + port, + cert_pem, + key_pem, + Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(2_000), + write_timeout: Duration::from_millis(2_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + max_transient_recv_errors: 20, + transient_recv_backoff: Duration::from_millis(100), + receiver_queue_capacity: 1000, + }, + ) + .await?; + + log!("TauriServer listening on port {}", port); + + while let Some((sender, mut receiver)) = host.next().await { + tokio::spawn(async move { + handle_connection(sender, &mut receiver).await; + }); + } + + Ok(()) +} + +async fn handle_connection(sender: Sender, receiver: &mut Receiver) { + let mut current_user_id = 0; + let sender = Arc::new(sender); + + while let Ok(cv) = receiver.receive().await { + match cv.get_type() { + CommunicationType::tauri_identification => { + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + if user_id != 0 { + current_user_id = user_id; + let conn = Arc::new(TauriConnection { + user_id, + sender: sender.clone(), + }); + + TAURI_CONNECTIONS + .entry(user_id) + .and_modify(|conns| { + conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); + conns.push(conn.clone()); + }) + .or_insert_with(|| vec![conn]); + + let response = + CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + if let Err(e) = sender.send(&response).await { + log!( + PrintType::General, + "Failed to send tauri success response: {}", + e + ); + } else { + log!(user_id, PrintType::Client, "Tauri device registered"); + } + } + } + CommunicationType::ping => { + let response = + CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + if let Err(_) = sender.send(&response).await { + break; + } + } + _ => {} + } + } + + // Cleanup + if current_user_id != 0 { + if let Some(mut conns) = TAURI_CONNECTIONS.get_mut(¤t_user_id) { + conns.retain(|c| !Arc::ptr_eq(c.sender.handle(), sender.handle())); + } + } +} + +pub async fn send_notification(user_id: i64, sender_id: i64) { + let conns_opt = { + let entry = TAURI_CONNECTIONS.get(&user_id); + entry.map(|e| e.clone()) + }; + + if let Some(conns) = conns_opt { + let cv = CommunicationValue::new(CommunicationType::push_notification) + .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + + let mut remove_needed = false; + for conn in conns.iter() { + if conn.sender.send(&cv).await.is_err() { + remove_needed = true; + } + } + + if remove_needed { + if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { + conns_mut.retain(|conn| conn.sender.is_open()); + } + } + } +} + +pub async fn remove_notification(user_id: i64, sender_id: i64) { + let conns_opt = { + let entry = TAURI_CONNECTIONS.get(&user_id); + entry.map(|e| e.clone()) + }; + + if let Some(conns) = conns_opt { + let cv = CommunicationValue::new(CommunicationType::read_notification) + .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + + let mut remove_needed = false; + for conn in conns.iter() { + if conn.sender.send(&cv).await.is_err() { + remove_needed = true; + } + } + + if remove_needed { + if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { + conns_mut.retain(|conn| conn.sender.is_open()); + } + } + } +} diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index d3453e0..8205775 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -966,8 +966,8 @@ impl OmikronConnection { cv: CommunicationValue, ) -> OmikronResult<()> { let user_id = cv.get_sender() as i64; - if let Ok(notifications) = sql::get_notifications(user_id).await { - let json_array: Vec = notifications + let response_array = match sql::get_notifications(user_id).await { + Ok(notifications) => notifications .into_iter() .map(|(sender, amount)| { DataValue::Container(vec![ @@ -975,59 +975,90 @@ impl OmikronConnection { (DataTypes::amount, DataValue::Number(amount)), ]) }) - .collect(); + .collect(), + Err(e) => { + log!(PrintType::General, "SQL get_notifications error: {}", e); + vec![] + } + }; - let response = CommunicationValue::new(CommunicationType::get_notifications) - .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(json_array)); - self.send(&response).await - } else { - Ok(()) - } + let response = CommunicationValue::new(CommunicationType::get_notifications) + .with_id(cv.get_id()) + .add_data(DataTypes::notifications, DataValue::Array(response_array)); + self.send(&response).await } async fn handle_read_notification( self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; + let receiver_id = match cv.get_sender() { + s if s > 0 => s as i64, + _ => match cv.get_data(DataTypes::receiver_id).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + if let Some(other_id) = cv .get_data(DataTypes::sender_id) .as_number() .map(|n| n as i64) { - if sql::read_notification(user_id, other_id).await.is_ok() { + if let Err(e) = sql::read_notification(receiver_id, other_id).await { + log!(PrintType::General, "SQL read_notification error: {}", e); + } else { let response = CommunicationValue::new(CommunicationType::read_notification) .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) + let _ = self.send(&response).await; + + // Sync with Tauri + crate::notifications::tauri::remove_notification(receiver_id, other_id).await; + + // Sync with other Omikron clients + let sync_cv = CommunicationValue::new(CommunicationType::read_notification) + .with_receiver(receiver_id as u64) + .add_data(DataTypes::sender_id, DataValue::Number(other_id)); + crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; } - } else { - Ok(()) } + Ok(()) } async fn handle_push_notification( self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - if let Some(other_id) = cv - .get_data(DataTypes::sender_id) - .as_number() - .map(|n| n as i64) - { - if sql::add_notification(user_id, other_id).await.is_ok() { - let response = CommunicationValue::new(CommunicationType::push_notification) - .with_id(cv.get_id()); - self.send(&response).await - } else { - Ok(()) - } + let receiver_id = match cv.get_receiver() { + r if r > 0 => r as i64, + _ => match cv.get_data(DataTypes::receiver_id).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + + let sender_id = match cv.get_data(DataTypes::sender_id).as_number() { + Some(id) => id as i64, + None => cv.get_sender() as i64, + }; + + if let Err(e) = sql::add_notification(receiver_id, sender_id).await { + log!(PrintType::General, "SQL add_notification error: {}", e); } else { - Ok(()) + let response = + CommunicationValue::new(CommunicationType::push_notification).with_id(cv.get_id()); + let _ = self.send(&response).await; + + // Sync with Tauri + crate::notifications::tauri::send_notification(receiver_id, sender_id).await; + + // Sync with other Omikron clients + let push_cv = CommunicationValue::new(CommunicationType::push_notification) + .with_receiver(receiver_id as u64) + .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; } + Ok(()) } async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index fbee4a9..70c73cd 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -3,6 +3,7 @@ use dashmap::DashMap; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; use std::sync::Arc; +use ttp_core::CommunicationValue; pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); @@ -24,6 +25,7 @@ pub async fn add_omikron(conn: Arc) { pub async fn remove_omikron(omikron_id: i64) { OMIKRON_CONNECTIONS.remove(&omikron_id); } + pub async fn get_random_omikron() -> Result, ()> { let mut rng = rand::thread_rng(); @@ -37,3 +39,11 @@ pub async fn get_random_omikron() -> Result, ()> { Err(()) } + +pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) { + if let Some(user_conn) = crate::sql::user_online_tracker::get_user_status(user_id) { + if let Some(omikron_conn) = OMIKRON_CONNECTIONS.get(&user_conn.omikron_id) { + let _ = omikron_conn.value().clone().send_message(cv).await; + } + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs index e2e6c92..7fa3947 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,5 @@ use std::{ - collections::{BTreeMap, HashMap}, + collections::BTreeMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -9,7 +9,6 @@ use std::{ }; use ansi_term::Color; -use json::JsonValue; use ttp_core::{CommunicationValue, DataTypes, DataValue}; static LOGGER: OnceLock> = OnceLock::new(); @@ -119,6 +118,16 @@ pub fn log_internal( #[macro_export] macro_rules! log { + // sender + actor + ($sender:expr, $kind:path, $($arg:tt)*) => { + $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) + }; + + // actor only + ($kind:path, $($arg:tt)*) => { + $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) + }; + // plain ($($arg:tt)*) => { $crate::util::logger::log_internal( @@ -129,27 +138,17 @@ macro_rules! log { format!($($arg)*) ) }; - - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(Some($sender), $kind, "", false, format!($($arg)*)) - }; - - // actor only - ($kind:expr, $($arg:tt)*) => { - $crate::util::logger::log_internal(None, $kind, "", false, format!($($arg)*)) - }; } #[macro_export] macro_rules! log_in { // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { + ($sender:expr, $kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(Some($sender), $kind, ">", false, format!($($arg)*)) }; // actor only - ($kind:expr, $($arg:tt)*) => { + ($kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(None, $kind, ">", false, format!($($arg)*)) }; @@ -167,14 +166,13 @@ macro_rules! log_in { #[macro_export] macro_rules! log_out { - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { + ($sender:expr, $kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(Some($sender), $kind, "<", false, format!($($arg)*)) }; // actor only - ($kind:expr, $($arg:tt)*) => { + ($kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(None, $kind, "<", false, format!($($arg)*)) }; @@ -192,14 +190,13 @@ macro_rules! log_out { #[macro_export] macro_rules! log_err { - // sender + actor - ($sender:expr, $kind:expr, $($arg:tt)*) => { + ($sender:expr, $kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(Some($sender), $kind, ">>", true, format!($($arg)*)) }; // actor only - ($kind:expr, $($arg:tt)*) => { + ($kind:path, $($arg:tt)*) => { $crate::util::logger::log_internal(None, $kind, ">>", true, format!($($arg)*)) }; From d87d97aaec0949477dd48c604db759005f3c94c2 Mon Sep 17 00:00:00 2001 From: Alois Date: Sun, 17 May 2026 22:37:10 +0200 Subject: [PATCH 133/220] [Chore] Update flake --- flake.lock | 2 +- flake.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.lock b/flake.lock index fb5bf79..c7ac9b8 100644 --- a/flake.lock +++ b/flake.lock @@ -82,13 +82,13 @@ "locked": { "lastModified": 1778948017, "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", + "ref": "refs/heads/main", "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "revCount": 117, "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" }, "original": { - "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" } diff --git a/flake.nix b/flake.nix index 97b1bcc..9930148 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; ttp = { - url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=7e5d1953df8592a1feba0f390d205d0ef61a3119"; + url = "git+https://git.methanium.net/Tensamin/TTP.git"; flake = false; }; }; From 8df919c9d83839c20905f8776edc9a33666a9892 Mon Sep 17 00:00:00 2001 From: Alois Date: Sun, 17 May 2026 22:37:10 +0200 Subject: [PATCH 134/220] [Chore] Update flake --- flake.lock | 2 +- flake.nix | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flake.lock b/flake.lock index fb5bf79..c7ac9b8 100644 --- a/flake.lock +++ b/flake.lock @@ -82,13 +82,13 @@ "locked": { "lastModified": 1778948017, "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", + "ref": "refs/heads/main", "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "revCount": 117, "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" }, "original": { - "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" } diff --git a/flake.nix b/flake.nix index 97b1bcc..9930148 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; ttp = { - url = "git+https://git.methanium.net/Tensamin/TTP.git?rev=7e5d1953df8592a1feba0f390d205d0ef61a3119"; + url = "git+https://git.methanium.net/Tensamin/TTP.git"; flake = false; }; }; From 25de1554fd3dcd3a9dc1c5a0e34f0d160c099158 Mon Sep 17 00:00:00 2001 From: Alois Date: Sun, 17 May 2026 22:53:23 +0200 Subject: [PATCH 135/220] [Fix] incorrect endpoints for ttp --- Cargo.lock | 2 ++ Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b34b40b..aa6be62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3599,6 +3599,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "base64", "byteorder", @@ -3611,6 +3612,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "quinn", "rustls", diff --git a/Cargo.toml b/Cargo.toml index c6c6bf4..22ec471 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -ttp-core = { path = "../ttp/core" } -ttp-native = { path = "../ttp/native" } +ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } +ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" From 1229d2edd25f54b5ba09b2b54634a04fdacb40b7 Mon Sep 17 00:00:00 2001 From: Alois Date: Sun, 17 May 2026 22:53:23 +0200 Subject: [PATCH 136/220] [Fix] incorrect endpoints for ttp --- Cargo.lock | 2 ++ Cargo.toml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b34b40b..aa6be62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3599,6 +3599,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "base64", "byteorder", @@ -3611,6 +3612,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" +source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" dependencies = [ "quinn", "rustls", diff --git a/Cargo.toml b/Cargo.toml index c6c6bf4..22ec471 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] -ttp-core = { path = "../ttp/core" } -ttp-native = { path = "../ttp/native" } +ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } +ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" From 30326eec77fe7ad0f1e45d39525f34154187327f Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 18 May 2026 20:47:09 +0200 Subject: [PATCH 137/220] Updated flake --- flake.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index c7ac9b8..e2048af 100644 --- a/flake.lock +++ b/flake.lock @@ -64,11 +64,11 @@ ] }, "locked": { - "lastModified": 1778987862, - "narHash": "sha256-V3qGt9P1eJP/r/1ONablphfGiH0RP4agQhrRANpDYx8=", + "lastModified": 1779074409, + "narHash": "sha256-6aXy8Ga41iLVM8ibddFU1O5+wYWcBGNEfZzZuL91eIc=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "6f44d8874ac29806c8d5cae42bf8e19ebb5ce0d3", + "rev": "2a77b5b1dc952f214e8102acdef1622b68515560", "type": "github" }, "original": { @@ -80,11 +80,11 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1778948017, - "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", + "lastModified": 1779051117, + "narHash": "sha256-W3F7hvMHeVKKSYSj159Z7eTbqXeQiObnAjBA78b5t3k=", "ref": "refs/heads/main", - "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", - "revCount": 117, + "rev": "7829ee8b296b06731bba3d1749f56767d58cf153", + "revCount": 121, "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" }, From e2e38b6fbbd2f52adeb388e45d092f9fcde441b7 Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 18 May 2026 20:47:09 +0200 Subject: [PATCH 138/220] Updated flake --- flake.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/flake.lock b/flake.lock index c7ac9b8..e2048af 100644 --- a/flake.lock +++ b/flake.lock @@ -64,11 +64,11 @@ ] }, "locked": { - "lastModified": 1778987862, - "narHash": "sha256-V3qGt9P1eJP/r/1ONablphfGiH0RP4agQhrRANpDYx8=", + "lastModified": 1779074409, + "narHash": "sha256-6aXy8Ga41iLVM8ibddFU1O5+wYWcBGNEfZzZuL91eIc=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "6f44d8874ac29806c8d5cae42bf8e19ebb5ce0d3", + "rev": "2a77b5b1dc952f214e8102acdef1622b68515560", "type": "github" }, "original": { @@ -80,11 +80,11 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1778948017, - "narHash": "sha256-hqBYSZnPq7f/F2Z6nJK+6a8ITk6WRCcVBcNT0CR6SnM=", + "lastModified": 1779051117, + "narHash": "sha256-W3F7hvMHeVKKSYSj159Z7eTbqXeQiObnAjBA78b5t3k=", "ref": "refs/heads/main", - "rev": "7e5d1953df8592a1feba0f390d205d0ef61a3119", - "revCount": 117, + "rev": "7829ee8b296b06731bba3d1749f56767d58cf153", + "revCount": 121, "type": "git", "url": "https://git.methanium.net/Tensamin/TTP.git" }, From ebfb69b0783570d4ca26488d0276e49cfae22859 Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 18 May 2026 21:25:19 +0200 Subject: [PATCH 139/220] [Fix] use working directory instead of executable directory --- src/main.rs | 6 ++++-- src/util/file_util.rs | 14 ++++++++++---- src/util/logger.rs | 6 ++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 67f8bb1..f92ccc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,14 @@ use crate::sql::sql::print_users; use crate::transport::omikron_connection; use crate::util::crypto_helper::load_public_key; use crate::util::crypto_helper::load_secret_key; +use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; -use dotenv::dotenv; +use dotenv::from_path; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; +use std::path::Path; static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); pub fn get_private_key() -> x448::Secret { @@ -32,7 +34,7 @@ async fn main() { println!("Error loading Provider"); return; } - dotenv().ok(); + let _ = from_path(Path::new(&get_directory()).join(".env")); startup(); log_in!("Incoming messages"); log_out!("Outgoing messages"); diff --git a/src/util/file_util.rs b/src/util/file_util.rs index f1c51db..593adbe 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -1,3 +1,4 @@ +use once_cell::sync::Lazy; use std::fs::{self, File}; use std::io::{self, BufReader, Read}; use std::path::{Path, PathBuf}; @@ -7,6 +8,10 @@ use zip::ZipArchive; use crate::log; +static WORKING_DIR: Lazy = Lazy::new(|| { + std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) +}); + pub fn delete_file(path: &str, name: &str) -> bool { let dir = Path::new(&get_directory()).join(path); let file = dir.join(name); @@ -163,10 +168,11 @@ pub fn get_children(path: &str) -> Vec { } pub fn get_directory() -> String { - std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .to_string_lossy() - .to_string() + WORKING_DIR.to_string_lossy().to_string() +} + +pub fn working_dir() -> &'static Path { + &WORKING_DIR } // Helper to download the zip file content to a file on disk diff --git a/src/util/logger.rs b/src/util/logger.rs index 7fa3947..845489f 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -11,6 +11,8 @@ use std::{ use ansi_term::Color; use ttp_core::{CommunicationValue, DataTypes, DataValue}; +use crate::util::file_util::get_directory; + static LOGGER: OnceLock> = OnceLock::new(); #[derive(Clone, Copy)] @@ -38,8 +40,8 @@ pub fn startup() { LOGGER.set(tx).expect("Logger already initialized"); thread::spawn(move || { - let log_dir = Path::new("logs"); - fs::create_dir_all(log_dir).expect("Failed to create log directory"); + let log_dir = Path::new(&get_directory()).join("logs"); + fs::create_dir_all(&log_dir).expect("Failed to create log directory"); let start_ts = SystemTime::now() .duration_since(UNIX_EPOCH) From 8331538bfe76c6a4e7ff26f019f170eb35cdfaa5 Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 18 May 2026 21:25:19 +0200 Subject: [PATCH 140/220] [Fix] use working directory instead of executable directory --- src/main.rs | 6 ++++-- src/util/file_util.rs | 14 ++++++++++---- src/util/logger.rs | 6 ++++-- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 67f8bb1..f92ccc4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,14 @@ use crate::sql::sql::print_users; use crate::transport::omikron_connection; use crate::util::crypto_helper::load_public_key; use crate::util::crypto_helper::load_secret_key; +use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; -use dotenv::dotenv; +use dotenv::from_path; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; +use std::path::Path; static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); pub fn get_private_key() -> x448::Secret { @@ -32,7 +34,7 @@ async fn main() { println!("Error loading Provider"); return; } - dotenv().ok(); + let _ = from_path(Path::new(&get_directory()).join(".env")); startup(); log_in!("Incoming messages"); log_out!("Outgoing messages"); diff --git a/src/util/file_util.rs b/src/util/file_util.rs index f1c51db..593adbe 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -1,3 +1,4 @@ +use once_cell::sync::Lazy; use std::fs::{self, File}; use std::io::{self, BufReader, Read}; use std::path::{Path, PathBuf}; @@ -7,6 +8,10 @@ use zip::ZipArchive; use crate::log; +static WORKING_DIR: Lazy = Lazy::new(|| { + std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) +}); + pub fn delete_file(path: &str, name: &str) -> bool { let dir = Path::new(&get_directory()).join(path); let file = dir.join(name); @@ -163,10 +168,11 @@ pub fn get_children(path: &str) -> Vec { } pub fn get_directory() -> String { - std::env::current_dir() - .unwrap_or_else(|_| PathBuf::from(".")) - .to_string_lossy() - .to_string() + WORKING_DIR.to_string_lossy().to_string() +} + +pub fn working_dir() -> &'static Path { + &WORKING_DIR } // Helper to download the zip file content to a file on disk diff --git a/src/util/logger.rs b/src/util/logger.rs index 7fa3947..845489f 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -11,6 +11,8 @@ use std::{ use ansi_term::Color; use ttp_core::{CommunicationValue, DataTypes, DataValue}; +use crate::util::file_util::get_directory; + static LOGGER: OnceLock> = OnceLock::new(); #[derive(Clone, Copy)] @@ -38,8 +40,8 @@ pub fn startup() { LOGGER.set(tx).expect("Logger already initialized"); thread::spawn(move || { - let log_dir = Path::new("logs"); - fs::create_dir_all(log_dir).expect("Failed to create log directory"); + let log_dir = Path::new(&get_directory()).join("logs"); + fs::create_dir_all(&log_dir).expect("Failed to create log directory"); let start_ts = SystemTime::now() .duration_since(UNIX_EPOCH) From aa262787b0b731a327d6ec452696052c3f76e95c Mon Sep 17 00:00:00 2001 From: Alois Date: Wed, 20 May 2026 21:11:01 +0200 Subject: [PATCH 141/220] [Chore] Update flake --- flake.lock | 18 +++++++++--------- flake.nix | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index e2048af..7c708a8 100644 --- a/flake.lock +++ b/flake.lock @@ -64,11 +64,11 @@ ] }, "locked": { - "lastModified": 1779074409, - "narHash": "sha256-6aXy8Ga41iLVM8ibddFU1O5+wYWcBGNEfZzZuL91eIc=", + "lastModified": 1779247103, + "narHash": "sha256-DwltBoBl9a7fCzlKi3xnNha1NHbfvawwkNdnTXEyfFQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "2a77b5b1dc952f214e8102acdef1622b68515560", + "rev": "86dbfb70dc1c2967245d87ed6d07d2c8bda305e3", "type": "github" }, "original": { @@ -80,17 +80,17 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1779051117, - "narHash": "sha256-W3F7hvMHeVKKSYSj159Z7eTbqXeQiObnAjBA78b5t3k=", + "lastModified": 1779304128, + "narHash": "sha256-C/z7JV4RxcuhdYE7vS+x4WJA1rXM/w03kjNfPPRi334=", "ref": "refs/heads/main", - "rev": "7829ee8b296b06731bba3d1749f56767d58cf153", - "revCount": 121, + "rev": "3e3f939f64088cb01c13348b58c504593953ec0e", + "revCount": 122, "type": "git", - "url": "https://git.methanium.net/Tensamin/TTP.git" + "url": "https://git.methanium.net/tensamin/ttp.git" }, "original": { "type": "git", - "url": "https://git.methanium.net/Tensamin/TTP.git" + "url": "https://git.methanium.net/tensamin/ttp.git" } } }, diff --git a/flake.nix b/flake.nix index 9930148..070a235 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; ttp = { - url = "git+https://git.methanium.net/Tensamin/TTP.git"; + url = "git+https://git.methanium.net/tensamin/ttp.git"; flake = false; }; }; From f57202a06d17be4bec44e7136c0d8b6445b82d09 Mon Sep 17 00:00:00 2001 From: Alois Date: Wed, 20 May 2026 21:11:01 +0200 Subject: [PATCH 142/220] [Chore] Update flake --- flake.lock | 18 +++++++++--------- flake.nix | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index e2048af..7c708a8 100644 --- a/flake.lock +++ b/flake.lock @@ -64,11 +64,11 @@ ] }, "locked": { - "lastModified": 1779074409, - "narHash": "sha256-6aXy8Ga41iLVM8ibddFU1O5+wYWcBGNEfZzZuL91eIc=", + "lastModified": 1779247103, + "narHash": "sha256-DwltBoBl9a7fCzlKi3xnNha1NHbfvawwkNdnTXEyfFQ=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "2a77b5b1dc952f214e8102acdef1622b68515560", + "rev": "86dbfb70dc1c2967245d87ed6d07d2c8bda305e3", "type": "github" }, "original": { @@ -80,17 +80,17 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1779051117, - "narHash": "sha256-W3F7hvMHeVKKSYSj159Z7eTbqXeQiObnAjBA78b5t3k=", + "lastModified": 1779304128, + "narHash": "sha256-C/z7JV4RxcuhdYE7vS+x4WJA1rXM/w03kjNfPPRi334=", "ref": "refs/heads/main", - "rev": "7829ee8b296b06731bba3d1749f56767d58cf153", - "revCount": 121, + "rev": "3e3f939f64088cb01c13348b58c504593953ec0e", + "revCount": 122, "type": "git", - "url": "https://git.methanium.net/Tensamin/TTP.git" + "url": "https://git.methanium.net/tensamin/ttp.git" }, "original": { "type": "git", - "url": "https://git.methanium.net/Tensamin/TTP.git" + "url": "https://git.methanium.net/tensamin/ttp.git" } } }, diff --git a/flake.nix b/flake.nix index 9930148..070a235 100644 --- a/flake.nix +++ b/flake.nix @@ -9,7 +9,7 @@ inputs.nixpkgs.follows = "nixpkgs"; }; ttp = { - url = "git+https://git.methanium.net/Tensamin/TTP.git"; + url = "git+https://git.methanium.net/tensamin/ttp.git"; flake = false; }; }; From 7dd1e8ce35ed2d68191cbe5357bd4497db973241 Mon Sep 17 00:00:00 2001 From: Alois Date: Wed, 20 May 2026 21:21:59 +0200 Subject: [PATCH 143/220] [Chore] Update crates --- Cargo.lock | 68 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa6be62..0962c69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "asn1-rs" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -505,9 +505,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -538,6 +538,15 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -848,9 +857,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -866,9 +875,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -978,7 +987,7 @@ checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", - "crypto-common 0.2.1", + "crypto-common 0.2.2", ] [[package]] @@ -1023,9 +1032,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -1526,9 +1535,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "typenum", ] @@ -1871,9 +1880,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libbz2-rs-sys" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" @@ -2066,9 +2075,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2538,9 +2547,9 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rcgen" -version = "0.14.7" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ "aws-lc-rs", "rustls-pki-types", @@ -3530,9 +3539,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags 2.11.1", "bytes", @@ -3599,7 +3608,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" +source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" dependencies = [ "base64", "byteorder", @@ -3612,7 +3621,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" +source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" dependencies = [ "quinn", "rustls", @@ -4375,10 +4384,11 @@ dependencies = [ [[package]] name = "yasna" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" dependencies = [ + "bit-vec", "time", ] @@ -4427,9 +4437,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] From 7743bac5c958519c9062c6cbb8711bb375e8698a Mon Sep 17 00:00:00 2001 From: Alois Date: Wed, 20 May 2026 21:21:59 +0200 Subject: [PATCH 144/220] [Chore] Update crates --- Cargo.lock | 68 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aa6be62..0962c69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -303,9 +303,9 @@ dependencies = [ [[package]] name = "asn1-rs" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", @@ -505,9 +505,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.16.3" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" dependencies = [ "cc", "cmake", @@ -538,6 +538,15 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -848,9 +857,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -866,9 +875,9 @@ dependencies = [ [[package]] name = "dashmap" -version = "6.1.0" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", "crossbeam-utils", @@ -978,7 +987,7 @@ checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.0", "const-oid 0.10.2", - "crypto-common 0.2.1", + "crypto-common 0.2.2", ] [[package]] @@ -1023,9 +1032,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" dependencies = [ "serde", ] @@ -1526,9 +1535,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d46837a0ed51fe95bd3b05de33cd64a1ee88fc797477ca48446872504507c5" +checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" dependencies = [ "typenum", ] @@ -1871,9 +1880,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libbz2-rs-sys" -version = "0.2.3" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3a6a8c165077efc8f3a971534c50ea6a1a18b329ef4a66e897a7e3a1494565f" +checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" @@ -2066,9 +2075,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2538,9 +2547,9 @@ checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rcgen" -version = "0.14.7" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ "aws-lc-rs", "rustls-pki-types", @@ -3530,9 +3539,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.10" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags 2.11.1", "bytes", @@ -3599,7 +3608,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" +source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" dependencies = [ "base64", "byteorder", @@ -3612,7 +3621,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#7e5d1953df8592a1feba0f390d205d0ef61a3119" +source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" dependencies = [ "quinn", "rustls", @@ -4375,10 +4384,11 @@ dependencies = [ [[package]] name = "yasna" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" dependencies = [ + "bit-vec", "time", ] @@ -4427,9 +4437,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] From 9580dbaf12f23d04bf3caaebabad32280ab8ec3f Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 21 May 2026 23:26:43 +0200 Subject: [PATCH 145/220] Reconnection --- src/transport/omikron_connection.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 8205775..a3dd099 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -734,7 +734,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -751,7 +751,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -796,7 +796,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -864,7 +864,7 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(error_message)); self.send(&response).await @@ -903,7 +903,7 @@ impl OmikronConnection { .with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(error_message)); self.send(&response).await @@ -936,7 +936,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -953,7 +953,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await From fa2ff487ffb8488407c38268c7275aae997032a0 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Thu, 21 May 2026 23:26:43 +0200 Subject: [PATCH 146/220] Reconnection --- src/transport/omikron_connection.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 8205775..a3dd099 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -734,7 +734,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -751,7 +751,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -796,7 +796,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -864,7 +864,7 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(error_message)); self.send(&response).await @@ -903,7 +903,7 @@ impl OmikronConnection { .with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(error_message)); self.send(&response).await @@ -936,7 +936,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await @@ -953,7 +953,7 @@ impl OmikronConnection { self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error) + let response = CommunicationValue::new(CommunicationType::error_internal) .with_id(cv.get_id()) .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); self.send(&response).await From b2520f41cb42cd2d5f8d565424cdaa60cc8321cc Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 22 May 2026 17:40:40 +0200 Subject: [PATCH 147/220] Changes for Dev --- Cargo.lock | 12 ++++++------ src/main.rs | 18 ++++++++++++++---- src/transport/omikron_connection.rs | 17 ++++++++++++----- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0962c69..0e4fe57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,9 +499,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" @@ -2917,9 +2917,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3608,7 +3608,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" +source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" dependencies = [ "base64", "byteorder", @@ -3621,7 +3621,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" +source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" dependencies = [ "quinn", "rustls", diff --git a/src/main.rs b/src/main.rs index f92ccc4..40bf10a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,14 +44,14 @@ async fn main() { .and_then(|s| s.parse().ok()) .unwrap_or(9187); - tokio::spawn(async move { + let omikron_handle = tokio::spawn(async move { match omikron_connection::start(omikron_port).await { Err(e) => log_err!(0, PrintType::General, "{:?}", e), _ => {} } }); - tokio::spawn(async move { + let tauri_handle = tokio::spawn(async move { match tauri::start(9189).await { Err(e) => log_err!(0, PrintType::General, "{:?}", e), _ => {} @@ -80,7 +80,17 @@ async fn main() { .and_then(|s| s.parse().ok()) .unwrap_or(9188); - let _ = server::server::start(api_port).await; + tokio::select! { + result = server::server::start(api_port) => { + if let Err(e) = result { + log_err!(0, PrintType::General, "Server error: {:?}", e); + } + } + _ = tokio::signal::ctrl_c() => { + log!("Shutting down on signal..."); + } + } - tokio::signal::ctrl_c().await.unwrap(); + omikron_handle.abort(); + tauri_handle.abort(); } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index a3dd099..acb0bff 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -103,7 +103,15 @@ pub struct OmikronConnection { pub_key: RwLock>>, pub ping: RwLock, waiting_tasks: DashMap, - cleanup_handle: Mutex>>, + cleanup_handle: std::sync::Mutex>>, +} + +impl Drop for OmikronConnection { + fn drop(&mut self) { + if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { + handle.abort(); + } + } } impl OmikronConnection { @@ -120,7 +128,7 @@ impl OmikronConnection { pub_key: RwLock::new(None), ping: RwLock::new(-1), waiting_tasks: DashMap::new(), - cleanup_handle: Mutex::new(None), + cleanup_handle: std::sync::Mutex::new(None), }); conn @@ -148,7 +156,7 @@ impl OmikronConnection { .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); } }); - *self.cleanup_handle.lock().await = Some(cleanup_handle); + *self.cleanup_handle.lock().unwrap() = Some(cleanup_handle); while let Ok(cv) = receiver.receive().await { if let Err(e) = self.clone().process_message(cv).await { @@ -1114,8 +1122,7 @@ impl OmikronConnection { } } - // Cancel cleanup task - if let Some(handle) = self.cleanup_handle.lock().await.take() { + if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); } } From 5509b7c03ed2e392df84a38f70a383d72413dcfc Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 22 May 2026 17:40:40 +0200 Subject: [PATCH 148/220] Changes for Dev --- Cargo.lock | 12 ++++++------ src/main.rs | 18 ++++++++++++++---- src/transport/omikron_connection.rs | 17 ++++++++++++----- 3 files changed, 32 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0962c69..0e4fe57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,9 +499,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" @@ -2917,9 +2917,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -3608,7 +3608,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" +source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" dependencies = [ "base64", "byteorder", @@ -3621,7 +3621,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#3e3f939f64088cb01c13348b58c504593953ec0e" +source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" dependencies = [ "quinn", "rustls", diff --git a/src/main.rs b/src/main.rs index f92ccc4..40bf10a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,14 +44,14 @@ async fn main() { .and_then(|s| s.parse().ok()) .unwrap_or(9187); - tokio::spawn(async move { + let omikron_handle = tokio::spawn(async move { match omikron_connection::start(omikron_port).await { Err(e) => log_err!(0, PrintType::General, "{:?}", e), _ => {} } }); - tokio::spawn(async move { + let tauri_handle = tokio::spawn(async move { match tauri::start(9189).await { Err(e) => log_err!(0, PrintType::General, "{:?}", e), _ => {} @@ -80,7 +80,17 @@ async fn main() { .and_then(|s| s.parse().ok()) .unwrap_or(9188); - let _ = server::server::start(api_port).await; + tokio::select! { + result = server::server::start(api_port) => { + if let Err(e) = result { + log_err!(0, PrintType::General, "Server error: {:?}", e); + } + } + _ = tokio::signal::ctrl_c() => { + log!("Shutting down on signal..."); + } + } - tokio::signal::ctrl_c().await.unwrap(); + omikron_handle.abort(); + tauri_handle.abort(); } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index a3dd099..acb0bff 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -103,7 +103,15 @@ pub struct OmikronConnection { pub_key: RwLock>>, pub ping: RwLock, waiting_tasks: DashMap, - cleanup_handle: Mutex>>, + cleanup_handle: std::sync::Mutex>>, +} + +impl Drop for OmikronConnection { + fn drop(&mut self) { + if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { + handle.abort(); + } + } } impl OmikronConnection { @@ -120,7 +128,7 @@ impl OmikronConnection { pub_key: RwLock::new(None), ping: RwLock::new(-1), waiting_tasks: DashMap::new(), - cleanup_handle: Mutex::new(None), + cleanup_handle: std::sync::Mutex::new(None), }); conn @@ -148,7 +156,7 @@ impl OmikronConnection { .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); } }); - *self.cleanup_handle.lock().await = Some(cleanup_handle); + *self.cleanup_handle.lock().unwrap() = Some(cleanup_handle); while let Ok(cv) = receiver.receive().await { if let Err(e) = self.clone().process_message(cv).await { @@ -1114,8 +1122,7 @@ impl OmikronConnection { } } - // Cancel cleanup task - if let Some(handle) = self.cleanup_handle.lock().await.take() { + if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); } } From fe6f1f16d6f6d36abb4488d7ebbdf921d2da1c64 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 23 May 2026 02:44:24 +0200 Subject: [PATCH 149/220] consistent Data in SQL, better Connections --- src/sql/sql.rs | 76 ++++++++++++++--------------- src/sql/user_online_tracker.rs | 4 +- src/transport/omikron_connection.rs | 23 ++++----- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 787d7d1..a34c72a 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -53,7 +53,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS users ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + id BIGINT NOT NULL PRIMARY KEY, username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, display VARCHAR(15) COLLATE utf8mb4_bin, status VARCHAR(15) COLLATE utf8mb4_bin, @@ -63,8 +63,8 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { sub_end BIGINT(20) NOT NULL DEFAULT 0, public_key TEXT NOT NULL COLLATE utf8mb4_bin, private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', - iota_id BIGINT UNSIGNED NOT NULL, - token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin + iota_id BIGINT NOT NULL, + token VARCHAR(256) NOT NULL UNIQUE COLLATE utf8mb4_bin )", ) .execute(&pool) @@ -72,7 +72,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS iotas ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + id BIGINT NOT NULL PRIMARY KEY, public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", ) @@ -81,7 +81,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS omikrons ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + id BIGINT NOT NULL PRIMARY KEY, public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin @@ -92,10 +92,10 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS notifications ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, - sender_id BIGINT UNSIGNED NOT NULL, - receiver_id BIGINT UNSIGNED NOT NULL, - amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + sender_id BIGINT NOT NULL, + receiver_id BIGINT NOT NULL, + amount BIGINT NOT NULL DEFAULT 0 )", ) .execute(&pool) @@ -243,7 +243,7 @@ pub async fn get_by_user_id( }; let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", ) .bind(id) .fetch_optional(&pool) @@ -311,7 +311,7 @@ pub async fn get_users_by_iota_id( }; let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", ) .bind(iota_id_param) .fetch_all(&pool) @@ -360,7 +360,7 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET username = ? WHERE id = ?") .bind(new_username) .bind(id) .execute(&pool) @@ -378,7 +378,7 @@ pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sql .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET display = ? WHERE id = ?") .bind(new_display) .bind(id) .execute(&pool) @@ -396,7 +396,7 @@ pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Erro .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") .bind(new_avatar) .bind(id) .execute(&pool) @@ -414,7 +414,7 @@ pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET about = ? WHERE id = ?") .bind(new_about) .bind(id) .execute(&pool) @@ -432,7 +432,7 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET status = ? WHERE id = ?") .bind(new_status) .bind(id) .execute(&pool) @@ -450,7 +450,7 @@ pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { .expect("Database pool not initialized") }; - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("DELETE FROM users WHERE id = ?") .bind(id) .execute(&pool) .await?; @@ -466,7 +466,7 @@ pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") .bind(new_iota_id) .bind(id) .execute(&pool) @@ -488,14 +488,12 @@ pub async fn change_keys( .expect("Database pool not initialized") }; - sqlx::query( - "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(new_public_key) - .bind(new_private_key_hash) - .bind(id) - .execute(&pool) - .await?; + sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(&pool) + .await?; Ok(()) } @@ -508,7 +506,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET token = ? WHERE id = ?") .bind(new_token) .bind(id) .execute(&pool) @@ -624,19 +622,17 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { .expect("Database pool not initialized") }; - let result = sqlx::query_as::<_, (u64, Vec)>( - "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await; + let result = + sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") + .bind(id) + .fetch_optional(&pool) + .await; match result { Ok(optional_row) => match optional_row { - Some((id_u64, public_key)) => Ok(( - id_u64 as i64, - String::from_utf8_lossy(&public_key).to_string(), - )), + Some((id_i64, public_key)) => { + Ok((id_i64, String::from_utf8_lossy(&public_key).to_string())) + } _ => Err(sqlx::Error::RowNotFound), }, Err(e) => Err(e), @@ -652,7 +648,7 @@ pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error .expect("Database pool not initialized") }; - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") .bind(new_key) .bind(id) .execute(&pool) @@ -670,7 +666,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { .expect("Database pool not initialized") }; - sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("DELETE FROM iotas WHERE id = ?") .bind(id) .execute(&pool) .await?; @@ -692,7 +688,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> }; let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", + "SELECT public_key, ip_address FROM omikrons WHERE id = ?", ) .bind(id) .fetch_optional(&pool) diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 42dfa22..304d5b5 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -133,9 +133,9 @@ pub async fn untrack_omikron(omikron_id: i64) { // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP for iota_id in offline_iotas { - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for user in users { - USER_STATUS_MAP.remove(&user.0); + USER_STATUS_MAP.remove(&(user.0 as i64)); } } // Finally remove the empty connections vector diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index acb0bff..63c8ccc 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -419,8 +419,8 @@ impl OmikronConnection { log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_online, + user_id.try_into().unwrap(), + UserStatus::user_offline, omikron_id, ); } @@ -448,11 +448,11 @@ impl OmikronConnection { user_online_tracker::track_iota_connection(iota_id, omikron_id, true); let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id)); + user_ids.push(DataValue::Number(user_id.try_into().unwrap())); user_online_tracker::track_user_status( - user_id, + user_id.try_into().unwrap(), UserStatus::user_offline, omikron_id, ); @@ -476,8 +476,8 @@ impl OmikronConnection { }; let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + let user_ids: Vec = users.iter().map(|u| u.0.try_into().unwrap()).collect(); user_online_tracker::untrack_many_users(&user_ids); } } @@ -489,7 +489,7 @@ impl OmikronConnection { if let DataValue::Number(user_id) = user_id_val { user_online_tracker::track_user_status( *user_id, - UserStatus::user_online, + UserStatus::user_offline, omikron_id, ); } @@ -911,9 +911,10 @@ impl OmikronConnection { .with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error_internal) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); + let response = + CommunicationValue::new(CommunicationType::error_internal) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); self.send(&response).await } } else { From 418deca3b6eaba9717ae8b850d75c57390508f45 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 23 May 2026 02:44:24 +0200 Subject: [PATCH 150/220] consistent Data in SQL, better Connections --- src/sql/sql.rs | 76 ++++++++++++++--------------- src/sql/user_online_tracker.rs | 4 +- src/transport/omikron_connection.rs | 23 ++++----- 3 files changed, 50 insertions(+), 53 deletions(-) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 787d7d1..a34c72a 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -53,7 +53,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS users ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + id BIGINT NOT NULL PRIMARY KEY, username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, display VARCHAR(15) COLLATE utf8mb4_bin, status VARCHAR(15) COLLATE utf8mb4_bin, @@ -63,8 +63,8 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { sub_end BIGINT(20) NOT NULL DEFAULT 0, public_key TEXT NOT NULL COLLATE utf8mb4_bin, private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', - iota_id BIGINT UNSIGNED NOT NULL, - token VARCHAR(255) NOT NULL UNIQUE COLLATE utf8mb4_bin + iota_id BIGINT NOT NULL, + token VARCHAR(256) NOT NULL UNIQUE COLLATE utf8mb4_bin )", ) .execute(&pool) @@ -72,7 +72,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS iotas ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + id BIGINT NOT NULL PRIMARY KEY, public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", ) @@ -81,7 +81,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS omikrons ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY, + id BIGINT NOT NULL PRIMARY KEY, public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin @@ -92,10 +92,10 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS notifications ( - id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO INCREMENT, - sender_id BIGINT UNSIGNED NOT NULL, - receiver_id BIGINT UNSIGNED NOT NULL, - amount BIGINT UNSIGNED NOT NULL DEFAULT 0 + id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, + sender_id BIGINT NOT NULL, + receiver_id BIGINT NOT NULL, + amount BIGINT NOT NULL DEFAULT 0 )", ) .execute(&pool) @@ -243,7 +243,7 @@ pub async fn get_by_user_id( }; let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = CAST(? AS UNSIGNED)", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", ) .bind(id) .fetch_optional(&pool) @@ -311,7 +311,7 @@ pub async fn get_users_by_iota_id( }; let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = CAST(? AS UNSIGNED)", + "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", ) .bind(iota_id_param) .fetch_all(&pool) @@ -360,7 +360,7 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET username = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET username = ? WHERE id = ?") .bind(new_username) .bind(id) .execute(&pool) @@ -378,7 +378,7 @@ pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sql .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET display = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET display = ? WHERE id = ?") .bind(new_display) .bind(id) .execute(&pool) @@ -396,7 +396,7 @@ pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Erro .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET avatar = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") .bind(new_avatar) .bind(id) .execute(&pool) @@ -414,7 +414,7 @@ pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET about = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET about = ? WHERE id = ?") .bind(new_about) .bind(id) .execute(&pool) @@ -432,7 +432,7 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET status = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET status = ? WHERE id = ?") .bind(new_status) .bind(id) .execute(&pool) @@ -450,7 +450,7 @@ pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { .expect("Database pool not initialized") }; - sqlx::query("DELETE FROM users WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("DELETE FROM users WHERE id = ?") .bind(id) .execute(&pool) .await?; @@ -466,7 +466,7 @@ pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET iota_id = CAST(? AS UNSIGNED) WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") .bind(new_iota_id) .bind(id) .execute(&pool) @@ -488,14 +488,12 @@ pub async fn change_keys( .expect("Database pool not initialized") }; - sqlx::query( - "UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(new_public_key) - .bind(new_private_key_hash) - .bind(id) - .execute(&pool) - .await?; + sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") + .bind(new_public_key) + .bind(new_private_key_hash) + .bind(id) + .execute(&pool) + .await?; Ok(()) } @@ -508,7 +506,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> .expect("Database pool not initialized") }; - sqlx::query("UPDATE users SET token = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE users SET token = ? WHERE id = ?") .bind(new_token) .bind(id) .execute(&pool) @@ -624,19 +622,17 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { .expect("Database pool not initialized") }; - let result = sqlx::query_as::<_, (u64, Vec)>( - "SELECT id, public_key FROM iotas WHERE id = CAST(? AS UNSIGNED)", - ) - .bind(id) - .fetch_optional(&pool) - .await; + let result = + sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") + .bind(id) + .fetch_optional(&pool) + .await; match result { Ok(optional_row) => match optional_row { - Some((id_u64, public_key)) => Ok(( - id_u64 as i64, - String::from_utf8_lossy(&public_key).to_string(), - )), + Some((id_i64, public_key)) => { + Ok((id_i64, String::from_utf8_lossy(&public_key).to_string())) + } _ => Err(sqlx::Error::RowNotFound), }, Err(e) => Err(e), @@ -652,7 +648,7 @@ pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error .expect("Database pool not initialized") }; - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") .bind(new_key) .bind(id) .execute(&pool) @@ -670,7 +666,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { .expect("Database pool not initialized") }; - sqlx::query("DELETE FROM iotas WHERE id = CAST(? AS UNSIGNED)") + sqlx::query("DELETE FROM iotas WHERE id = ?") .bind(id) .execute(&pool) .await?; @@ -692,7 +688,7 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> }; let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = CAST(? AS UNSIGNED)", + "SELECT public_key, ip_address FROM omikrons WHERE id = ?", ) .bind(id) .fetch_optional(&pool) diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 42dfa22..304d5b5 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -133,9 +133,9 @@ pub async fn untrack_omikron(omikron_id: i64) { // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP for iota_id in offline_iotas { - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id).await { + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for user in users { - USER_STATUS_MAP.remove(&user.0); + USER_STATUS_MAP.remove(&(user.0 as i64)); } } // Finally remove the empty connections vector diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index acb0bff..63c8ccc 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -419,8 +419,8 @@ impl OmikronConnection { log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_online, + user_id.try_into().unwrap(), + UserStatus::user_offline, omikron_id, ); } @@ -448,11 +448,11 @@ impl OmikronConnection { user_online_tracker::track_iota_connection(iota_id, omikron_id, true); let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { + if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id)); + user_ids.push(DataValue::Number(user_id.try_into().unwrap())); user_online_tracker::track_user_status( - user_id, + user_id.try_into().unwrap(), UserStatus::user_offline, omikron_id, ); @@ -476,8 +476,8 @@ impl OmikronConnection { }; let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id).await { - let user_ids: Vec = users.iter().map(|u| u.0).collect(); + if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + let user_ids: Vec = users.iter().map(|u| u.0.try_into().unwrap()).collect(); user_online_tracker::untrack_many_users(&user_ids); } } @@ -489,7 +489,7 @@ impl OmikronConnection { if let DataValue::Number(user_id) = user_id_val { user_online_tracker::track_user_status( *user_id, - UserStatus::user_online, + UserStatus::user_offline, omikron_id, ); } @@ -911,9 +911,10 @@ impl OmikronConnection { .with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error_internal) - .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); + let response = + CommunicationValue::new(CommunicationType::error_internal) + .with_id(cv.get_id()) + .add_data(DataTypes::error_type, DataValue::Str(error_message)); self.send(&response).await } } else { From 138d00c8622310454f590d1f0d3380927c88330b Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 23 May 2026 18:30:03 +0200 Subject: [PATCH 151/220] [FIX] Correct Data in SQL --- src/sql/sql.rs | 66 +++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index a34c72a..d9eb712 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -185,29 +185,29 @@ pub async fn get_by_username( Some(row) => { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); + let public_key: Vec = row.get("public_key"); + let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); Ok(( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map(|d| String::from_utf8_lossy(&d).to_string()), status.map(|s| String::from_utf8_lossy(&s).to_string()), about.map(|a| String::from_utf8_lossy(&a).to_string()), avatar, sub_level, sub_end, - public_key, - private_key_hash, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) } @@ -253,29 +253,29 @@ pub async fn get_by_user_id( Some(row) => { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); + let public_key: Vec = row.get("public_key"); + let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); Ok(( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map(|d| String::from_utf8_lossy(&d).to_string()), status.map(|s| String::from_utf8_lossy(&s).to_string()), about.map(|a| String::from_utf8_lossy(&a).to_string()), avatar, sub_level, sub_end, - public_key, - private_key_hash, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) } @@ -321,29 +321,29 @@ pub async fn get_users_by_iota_id( for row in rows { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); + let public_key: Vec = row.get("public_key"); + let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); users.push(( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map(|d| String::from_utf8_lossy(&d).to_string()), status.map(|s| String::from_utf8_lossy(&s).to_string()), about.map(|a| String::from_utf8_lossy(&a).to_string()), avatar, sub_level, sub_end, - public_key, - private_key_hash, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )); } @@ -361,7 +361,7 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: }; sqlx::query("UPDATE users SET username = ? WHERE id = ?") - .bind(new_username) + .bind(new_username.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -379,7 +379,7 @@ pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sql }; sqlx::query("UPDATE users SET display = ? WHERE id = ?") - .bind(new_display) + .bind(new_display.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -397,7 +397,7 @@ pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Erro }; sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") - .bind(new_avatar) + .bind(new_avatar.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -415,7 +415,7 @@ pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> }; sqlx::query("UPDATE users SET about = ? WHERE id = ?") - .bind(new_about) + .bind(new_about.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -433,7 +433,7 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro }; sqlx::query("UPDATE users SET status = ? WHERE id = ?") - .bind(new_status) + .bind(new_status.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -489,8 +489,8 @@ pub async fn change_keys( }; sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key) - .bind(new_private_key_hash) + .bind(new_public_key.as_bytes().to_vec()) + .bind(new_private_key_hash.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -507,7 +507,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> }; sqlx::query("UPDATE users SET token = ? WHERE id = ?") - .bind(new_token) + .bind(new_token.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -533,10 +533,10 @@ pub async fn register_complete_user( "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", ) .bind(id) - .bind(username) - .bind(public_key) + .bind(username.as_bytes().to_vec()) + .bind(public_key.as_bytes().to_vec()) .bind(iota_id) - .bind(token) + .bind(token.as_bytes().to_vec()) .execute(&pool) .await?; @@ -561,7 +561,7 @@ pub async fn print_users() -> Result<(), Box> { { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); @@ -573,7 +573,7 @@ pub async fn print_users() -> Result<(), Box> { ( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), @@ -606,7 +606,7 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id) - .bind(public_key) + .bind(public_key.as_bytes().to_vec()) .execute(&pool) .await?; @@ -649,7 +649,7 @@ pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error }; sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(new_key) + .bind(new_key.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; From 0b5dda49c9477930138c74b889f46ee0b668ab16 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 23 May 2026 18:30:03 +0200 Subject: [PATCH 152/220] [FIX] Correct Data in SQL --- src/sql/sql.rs | 66 +++++++++++++++++++++++++------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/sql/sql.rs b/src/sql/sql.rs index a34c72a..d9eb712 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -185,29 +185,29 @@ pub async fn get_by_username( Some(row) => { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); + let public_key: Vec = row.get("public_key"); + let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); Ok(( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map(|d| String::from_utf8_lossy(&d).to_string()), status.map(|s| String::from_utf8_lossy(&s).to_string()), about.map(|a| String::from_utf8_lossy(&a).to_string()), avatar, sub_level, sub_end, - public_key, - private_key_hash, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) } @@ -253,29 +253,29 @@ pub async fn get_by_user_id( Some(row) => { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); + let public_key: Vec = row.get("public_key"); + let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); Ok(( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map(|d| String::from_utf8_lossy(&d).to_string()), status.map(|s| String::from_utf8_lossy(&s).to_string()), about.map(|a| String::from_utf8_lossy(&a).to_string()), avatar, sub_level, sub_end, - public_key, - private_key_hash, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) } @@ -321,29 +321,29 @@ pub async fn get_users_by_iota_id( for row in rows { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: String = row.get("public_key"); - let private_key_hash: String = row.get("private_key_hash"); + let public_key: Vec = row.get("public_key"); + let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); users.push(( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map(|d| String::from_utf8_lossy(&d).to_string()), status.map(|s| String::from_utf8_lossy(&s).to_string()), about.map(|a| String::from_utf8_lossy(&a).to_string()), avatar, sub_level, sub_end, - public_key, - private_key_hash, + String::from_utf8_lossy(&public_key).to_string(), + String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )); } @@ -361,7 +361,7 @@ pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx:: }; sqlx::query("UPDATE users SET username = ? WHERE id = ?") - .bind(new_username) + .bind(new_username.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -379,7 +379,7 @@ pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sql }; sqlx::query("UPDATE users SET display = ? WHERE id = ?") - .bind(new_display) + .bind(new_display.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -397,7 +397,7 @@ pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Erro }; sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") - .bind(new_avatar) + .bind(new_avatar.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -415,7 +415,7 @@ pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> }; sqlx::query("UPDATE users SET about = ? WHERE id = ?") - .bind(new_about) + .bind(new_about.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -433,7 +433,7 @@ pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Erro }; sqlx::query("UPDATE users SET status = ? WHERE id = ?") - .bind(new_status) + .bind(new_status.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -489,8 +489,8 @@ pub async fn change_keys( }; sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key) - .bind(new_private_key_hash) + .bind(new_public_key.as_bytes().to_vec()) + .bind(new_private_key_hash.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -507,7 +507,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> }; sqlx::query("UPDATE users SET token = ? WHERE id = ?") - .bind(new_token) + .bind(new_token.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; @@ -533,10 +533,10 @@ pub async fn register_complete_user( "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", ) .bind(id) - .bind(username) - .bind(public_key) + .bind(username.as_bytes().to_vec()) + .bind(public_key.as_bytes().to_vec()) .bind(iota_id) - .bind(token) + .bind(token.as_bytes().to_vec()) .execute(&pool) .await?; @@ -561,7 +561,7 @@ pub async fn print_users() -> Result<(), Box> { { let id: i64 = row.get("id"); let iota_id: i64 = row.get("iota_id"); - let username: String = row.get("username"); + let username: Vec = row.get("username"); let display: Option> = row.get("display"); let status: Option> = row.get("status"); let about: Option> = row.get("about"); @@ -573,7 +573,7 @@ pub async fn print_users() -> Result<(), Box> { ( id, iota_id, - username, + String::from_utf8_lossy(&username).to_string(), display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), @@ -606,7 +606,7 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id) - .bind(public_key) + .bind(public_key.as_bytes().to_vec()) .execute(&pool) .await?; @@ -649,7 +649,7 @@ pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error }; sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(new_key) + .bind(new_key.as_bytes().to_vec()) .bind(id) .execute(&pool) .await?; From cce9454ca8fae9b192736e036aaff67560cab75a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:55:21 +0200 Subject: [PATCH 153/220] Message States, Tauri, Global and Local Settings --- Cargo.lock | 176 ++++++++++++++-------------- src/sql/connection_status.rs | 1 + src/transport/omikron_connection.rs | 49 +++++++- 3 files changed, 136 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e4fe57..5f83697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "bytes", "futures-core", "futures-sink", @@ -31,7 +31,7 @@ dependencies = [ "actix-tls", "actix-utils", "base64", - "bitflags 2.11.1", + "bitflags 2.12.1", "brotli", "bytes", "bytestring", @@ -188,7 +188,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.3", + "socket2 0.6.4", "time", "tracing", "url", @@ -555,9 +555,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" dependencies = [ "serde_core", ] @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -616,9 +616,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -652,9 +652,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.62" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -992,9 +992,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1380,7 +1380,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.4.1", "indexmap", "slab", "tokio", @@ -1490,9 +1490,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1505,7 +1505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.1", ] [[package]] @@ -1516,7 +1516,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body", "pin-project-lite", ] @@ -1544,16 +1544,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2 0.4.14", - "http 1.4.0", + "http 1.4.1", "http-body", "httparse", "itoa", @@ -1569,7 +1569,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", + "http 1.4.1", "hyper", "hyper-util", "rustls", @@ -1588,14 +1588,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -1832,9 +1832,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1898,14 +1898,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "libc", "plain", - "redox_syscall 0.7.5", + "redox_syscall 0.8.1", ] [[package]] @@ -1964,9 +1964,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" dependencies = [ "value-bag", ] @@ -1999,9 +1999,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -2027,9 +2027,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -2399,7 +2399,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2 0.6.4", "thiserror", "tokio", "tracing", @@ -2437,7 +2437,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -2564,16 +2564,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", ] [[package]] name = "redox_syscall" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", ] [[package]] @@ -2613,16 +2613,16 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "h2 0.4.14", - "http 1.4.0", + "http 1.4.1", "http-body", "http-body-util", "hyper", @@ -2727,7 +2727,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2752,9 +2752,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2862,7 +2862,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2986,9 +2986,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3069,9 +3069,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3189,7 +3189,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.11.1", + "bitflags 2.12.1", "byteorder", "bytes", "crc", @@ -3231,7 +3231,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.11.1", + "bitflags 2.12.1", "byteorder", "crc", "dotenvy", @@ -3380,7 +3380,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3483,7 +3483,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] @@ -3543,10 +3543,10 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "bytes", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body", "pin-project-lite", "tower", @@ -3608,7 +3608,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" +source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" dependencies = [ "base64", "byteorder", @@ -3621,7 +3621,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" +source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" dependencies = [ "quinn", "rustls", @@ -3634,9 +3634,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-bidi" @@ -3667,9 +3667,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -3719,9 +3719,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3803,9 +3803,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3816,9 +3816,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3826,9 +3826,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3836,9 +3836,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3849,9 +3849,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3884,7 +3884,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -3892,9 +3892,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -4281,7 +4281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.12.1", "indexmap", "log", "serde", @@ -4331,7 +4331,7 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2 0.6.3", + "socket2 0.6.4", "thiserror", "time", "tokio", @@ -4417,18 +4417,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index a175e6d..8587ffe 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -10,6 +10,7 @@ pub enum UserStatus { user_idle, user_wc, user_borked, + user_invisible, iota_offline, iota_online, iota_borked, diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 63c8ccc..93804fd 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -381,6 +381,7 @@ impl OmikronConnection { CommunicationType::get_notifications => self.handle_get_notifications(cv).await, CommunicationType::read_notification => self.handle_read_notification(cv).await, CommunicationType::push_notification => self.handle_push_notification(cv).await, + CommunicationType::get_states => self.handle_get_states(cv).await, _ => { log_err!( @@ -418,9 +419,14 @@ impl OmikronConnection { async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + let status = cv + .get_data(DataTypes::user_state) + .as_str() + .and_then(|s| UserStatus::from_str(s)) + .unwrap_or(UserStatus::user_online); user_online_tracker::track_user_status( user_id.try_into().unwrap(), - UserStatus::user_offline, + status, omikron_id, ); } @@ -597,9 +603,14 @@ impl OmikronConnection { user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); if let Some(us) = user_status { + let display_status = if us.connection_type == UserStatus::user_invisible { + UserStatus::user_offline + } else { + us.connection_type.clone() + }; response = response.add_data( DataTypes::online_status, - DataValue::Str(us.connection_type.to_string()), + DataValue::Str(display_status.to_string()), ); response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); } else { @@ -1070,6 +1081,40 @@ impl OmikronConnection { Ok(()) } + async fn handle_get_states(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_ids = match cv.get_data(DataTypes::user_ids) { + DataValue::Array(ids) => ids, + _ => return Ok(()), + }; + + let mut states = Vec::new(); + for id_val in user_ids { + if let DataValue::Number(user_id) = id_val { + let user_id = *user_id; + let status = user_online_tracker::get_user_status(user_id); + let status_str = match status { + Some(ref us) => { + if us.connection_type == UserStatus::user_invisible { + "user_offline".to_string() + } else { + us.connection_type.to_string() + } + } + None => UserStatus::iota_offline.to_string(), + }; + let mut map = Vec::new(); + map.push((DataTypes::user_id, DataValue::Number(user_id))); + map.push((DataTypes::user_state, DataValue::Str(status_str))); + states.push(DataValue::Container(map)); + } + } + + let response = CommunicationValue::new(CommunicationType::get_states) + .with_id(cv.get_id()) + .add_data(DataTypes::user_states, DataValue::Array(states)); + self.send(&response).await + } + async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { *self.ping.write().await = *last_ping; From acc25348343d8059b262df6475c6cf1463e22328 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:55:21 +0200 Subject: [PATCH 154/220] Message States, Tauri, Global and Local Settings --- Cargo.lock | 176 ++++++++++++++-------------- src/sql/connection_status.rs | 1 + src/transport/omikron_connection.rs | 49 +++++++- 3 files changed, 136 insertions(+), 90 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0e4fe57..5f83697 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "bytes", "futures-core", "futures-sink", @@ -31,7 +31,7 @@ dependencies = [ "actix-tls", "actix-utils", "base64", - "bitflags 2.11.1", + "bitflags 2.12.1", "brotli", "bytes", "bytestring", @@ -188,7 +188,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.6.3", + "socket2 0.6.4", "time", "tracing", "url", @@ -555,9 +555,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" dependencies = [ "serde_core", ] @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.2" +version = "8.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.0" +version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -616,9 +616,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "byteorder" @@ -652,9 +652,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.62" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -992,9 +992,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -1380,7 +1380,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http 1.4.1", "indexmap", "slab", "tokio", @@ -1490,9 +1490,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" dependencies = [ "bytes", "itoa", @@ -1505,7 +1505,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http 1.4.1", ] [[package]] @@ -1516,7 +1516,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http 1.4.1", "http-body", "pin-project-lite", ] @@ -1544,16 +1544,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", "futures-channel", "futures-core", "h2 0.4.14", - "http 1.4.0", + "http 1.4.1", "http-body", "httparse", "itoa", @@ -1569,7 +1569,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.0", + "http 1.4.1", "hyper", "hyper-util", "rustls", @@ -1588,14 +1588,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.6.4", "system-configuration", "tokio", "tower-service", @@ -1832,9 +1832,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" dependencies = [ "cfg-if", "futures-util", @@ -1898,14 +1898,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "libc", "plain", - "redox_syscall 0.7.5", + "redox_syscall 0.8.1", ] [[package]] @@ -1964,9 +1964,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" dependencies = [ "value-bag", ] @@ -1999,9 +1999,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -2027,9 +2027,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "log", @@ -2399,7 +2399,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2 0.6.4", "thiserror", "tokio", "tracing", @@ -2437,7 +2437,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -2564,16 +2564,16 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", ] [[package]] name = "redox_syscall" -version = "0.7.5" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4666a1a60d8412eab19d94f6d13dcc9cea0a5ef4fdf6a5db306537413c661b1b" +checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", ] [[package]] @@ -2613,16 +2613,16 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "reqwest" -version = "0.13.3" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "h2 0.4.14", - "http 1.4.0", + "http 1.4.1", "http-body", "http-body-util", "hyper", @@ -2727,7 +2727,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2752,9 +2752,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2862,7 +2862,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2986,9 +2986,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -3069,9 +3069,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3189,7 +3189,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.11.1", + "bitflags 2.12.1", "byteorder", "bytes", "crc", @@ -3231,7 +3231,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.11.1", + "bitflags 2.12.1", "byteorder", "crc", "dotenvy", @@ -3380,7 +3380,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3483,7 +3483,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.4", "tokio-macros", "windows-sys 0.61.2", ] @@ -3543,10 +3543,10 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "bytes", "futures-util", - "http 1.4.0", + "http 1.4.1", "http-body", "pin-project-lite", "tower", @@ -3608,7 +3608,7 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "ttp-core" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" +source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" dependencies = [ "base64", "byteorder", @@ -3621,7 +3621,7 @@ dependencies = [ [[package]] name = "ttp-native" version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#886edbf51d26636e46745c3637b80610df82f5dc" +source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" dependencies = [ "quinn", "rustls", @@ -3634,9 +3634,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-bidi" @@ -3667,9 +3667,9 @@ checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -3719,9 +3719,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.1" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -3803,9 +3803,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" dependencies = [ "cfg-if", "once_cell", @@ -3816,9 +3816,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.71" +version = "0.4.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" dependencies = [ "js-sys", "wasm-bindgen", @@ -3826,9 +3826,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3836,9 +3836,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" dependencies = [ "bumpalo", "proc-macro2", @@ -3849,9 +3849,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.121" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" dependencies = [ "unicode-ident", ] @@ -3884,7 +3884,7 @@ version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ - "bitflags 2.11.1", + "bitflags 2.12.1", "hashbrown 0.15.5", "indexmap", "semver", @@ -3892,9 +3892,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.98" +version = "0.3.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" +checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" dependencies = [ "js-sys", "wasm-bindgen", @@ -4281,7 +4281,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", - "bitflags 2.11.1", + "bitflags 2.12.1", "indexmap", "log", "serde", @@ -4331,7 +4331,7 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2 0.6.3", + "socket2 0.6.4", "thiserror", "time", "tokio", @@ -4417,18 +4417,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" dependencies = [ "proc-macro2", "quote", diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index a175e6d..8587ffe 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -10,6 +10,7 @@ pub enum UserStatus { user_idle, user_wc, user_borked, + user_invisible, iota_offline, iota_online, iota_borked, diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 63c8ccc..93804fd 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -381,6 +381,7 @@ impl OmikronConnection { CommunicationType::get_notifications => self.handle_get_notifications(cv).await, CommunicationType::read_notification => self.handle_read_notification(cv).await, CommunicationType::push_notification => self.handle_push_notification(cv).await, + CommunicationType::get_states => self.handle_get_states(cv).await, _ => { log_err!( @@ -418,9 +419,14 @@ impl OmikronConnection { async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "User connected"); if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + let status = cv + .get_data(DataTypes::user_state) + .as_str() + .and_then(|s| UserStatus::from_str(s)) + .unwrap_or(UserStatus::user_online); user_online_tracker::track_user_status( user_id.try_into().unwrap(), - UserStatus::user_offline, + status, omikron_id, ); } @@ -597,9 +603,14 @@ impl OmikronConnection { user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); if let Some(us) = user_status { + let display_status = if us.connection_type == UserStatus::user_invisible { + UserStatus::user_offline + } else { + us.connection_type.clone() + }; response = response.add_data( DataTypes::online_status, - DataValue::Str(us.connection_type.to_string()), + DataValue::Str(display_status.to_string()), ); response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); } else { @@ -1070,6 +1081,40 @@ impl OmikronConnection { Ok(()) } + async fn handle_get_states(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { + let user_ids = match cv.get_data(DataTypes::user_ids) { + DataValue::Array(ids) => ids, + _ => return Ok(()), + }; + + let mut states = Vec::new(); + for id_val in user_ids { + if let DataValue::Number(user_id) = id_val { + let user_id = *user_id; + let status = user_online_tracker::get_user_status(user_id); + let status_str = match status { + Some(ref us) => { + if us.connection_type == UserStatus::user_invisible { + "user_offline".to_string() + } else { + us.connection_type.to_string() + } + } + None => UserStatus::iota_offline.to_string(), + }; + let mut map = Vec::new(); + map.push((DataTypes::user_id, DataValue::Number(user_id))); + map.push((DataTypes::user_state, DataValue::Str(status_str))); + states.push(DataValue::Container(map)); + } + } + + let response = CommunicationValue::new(CommunicationType::get_states) + .with_id(cv.get_id()) + .add_data(DataTypes::user_states, DataValue::Array(states)); + self.send(&response).await + } + async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { *self.ping.write().await = *last_ping; From dc6a4ea0947ea0efa717fc4a9a492663114c378a Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 4 Jun 2026 13:36:08 +0200 Subject: [PATCH 155/220] Update nix flake --- flake.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index 7c708a8..0d30539 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778869304, - "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", + "lastModified": 1780243769, + "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", + "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c", "type": "github" }, "original": { @@ -64,11 +64,11 @@ ] }, "locked": { - "lastModified": 1779247103, - "narHash": "sha256-DwltBoBl9a7fCzlKi3xnNha1NHbfvawwkNdnTXEyfFQ=", + "lastModified": 1780543271, + "narHash": "sha256-oPJ7eJN1sM37v92Rp/eyQL7/rUm0BOvXEBAoq/zN0cM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "86dbfb70dc1c2967245d87ed6d07d2c8bda305e3", + "rev": "c30ca201c5093540cf792f6982f81ba1aa0f3514", "type": "github" }, "original": { @@ -80,11 +80,11 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1779304128, - "narHash": "sha256-C/z7JV4RxcuhdYE7vS+x4WJA1rXM/w03kjNfPPRi334=", + "lastModified": 1780494955, + "narHash": "sha256-i2VRRF6yNips3c4JHgfvmvMxb0HTkTCn69lmsKZLHRw=", "ref": "refs/heads/main", - "rev": "3e3f939f64088cb01c13348b58c504593953ec0e", - "revCount": 122, + "rev": "23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2", + "revCount": 125, "type": "git", "url": "https://git.methanium.net/tensamin/ttp.git" }, From 17b7862ceaa098a994d3165414856307e3fc5a2f Mon Sep 17 00:00:00 2001 From: Alois Date: Thu, 4 Jun 2026 13:36:08 +0200 Subject: [PATCH 156/220] Update nix flake --- flake.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/flake.lock b/flake.lock index 7c708a8..0d30539 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1778869304, - "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", + "lastModified": 1780243769, + "narHash": "sha256-x5UQuRsH3MqI0U9afaXSNqzTPSeZlRLvFAav2Ux1pNw=", "owner": "nixos", "repo": "nixpkgs", - "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", + "rev": "331800de5053fcebacf6813adb5db9c9dca22a0c", "type": "github" }, "original": { @@ -64,11 +64,11 @@ ] }, "locked": { - "lastModified": 1779247103, - "narHash": "sha256-DwltBoBl9a7fCzlKi3xnNha1NHbfvawwkNdnTXEyfFQ=", + "lastModified": 1780543271, + "narHash": "sha256-oPJ7eJN1sM37v92Rp/eyQL7/rUm0BOvXEBAoq/zN0cM=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "86dbfb70dc1c2967245d87ed6d07d2c8bda305e3", + "rev": "c30ca201c5093540cf792f6982f81ba1aa0f3514", "type": "github" }, "original": { @@ -80,11 +80,11 @@ "ttp": { "flake": false, "locked": { - "lastModified": 1779304128, - "narHash": "sha256-C/z7JV4RxcuhdYE7vS+x4WJA1rXM/w03kjNfPPRi334=", + "lastModified": 1780494955, + "narHash": "sha256-i2VRRF6yNips3c4JHgfvmvMxb0HTkTCn69lmsKZLHRw=", "ref": "refs/heads/main", - "rev": "3e3f939f64088cb01c13348b58c504593953ec0e", - "revCount": 122, + "rev": "23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2", + "revCount": 125, "type": "git", "url": "https://git.methanium.net/tensamin/ttp.git" }, From fd43f17292450336bb3bc0fdbe02cc81cb88dba8 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:51:17 +0200 Subject: [PATCH 157/220] Initial migration to MTP [Broken] --- .cargo/config.toml | 2 + Cargo.lock | 551 +++++++++++----------------- Cargo.toml | 3 +- src/notifications/tauri.rs | 81 ++-- src/server/server.rs | 2 +- src/server/short_link.rs | 2 +- src/transport/omikron_connection.rs | 341 +++++++++-------- src/transport/omikron_manager.rs | 2 +- src/util/logger.rs | 28 +- type-maps.yaml | 240 ++++++++++++ 10 files changed, 691 insertions(+), 561 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 type-maps.yaml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..d363b83 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true } diff --git a/Cargo.lock b/Cargo.lock index 5f83697..1f85cb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "bytes", "futures-core", "futures-sink", @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.1" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" dependencies = [ "actix-codec", "actix-rt", @@ -31,14 +31,14 @@ dependencies = [ "actix-tls", "actix-utils", "base64", - "bitflags 2.12.1", + "bitflags 2.13.0", "brotli", "bytes", "bytestring", "derive_more", "encoding_rs", "flate2", - "foldhash", + "foldhash 0.2.0", "futures-core", "h2 0.3.27", "http 0.2.12", @@ -131,7 +131,7 @@ dependencies = [ "actix-service", "actix-utils", "futures-core", - "impl-more", + "impl-more 0.1.9", "pin-project-lite", "rustls-pki-types", "tokio", @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.13.0" +version = "4.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" dependencies = [ "actix-codec", "actix-http", @@ -172,10 +172,10 @@ dependencies = [ "cookie", "derive_more", "encoding_rs", - "foldhash", + "foldhash 0.2.0", "futures-core", "futures-util", - "impl-more", + "impl-more 0.3.1", "itoa", "language-tags", "log", @@ -264,9 +264,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "arbitrary" @@ -555,9 +555,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -573,9 +573,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.3" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.1" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -628,9 +628,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "bytestring" @@ -652,9 +652,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -676,9 +676,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -929,9 +929,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_arbitrary" @@ -985,7 +982,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", ] @@ -1163,6 +1160,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1316,16 +1319,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", ] [[package]] @@ -1371,16 +1372,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.1", + "http 1.4.2", "indexmap", "slab", "tokio", @@ -1402,7 +1403,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1490,9 +1491,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1505,7 +1506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.1", + "http 1.4.2", ] [[package]] @@ -1516,7 +1517,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.1", + "http 1.4.2", "http-body", "pin-project-lite", ] @@ -1535,9 +1536,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -1552,8 +1553,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.14", - "http 1.4.1", + "h2 0.4.15", + "http 1.4.2", "http-body", "httparse", "itoa", @@ -1569,7 +1570,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.1", + "http 1.4.2", "hyper", "hyper-util", "rustls", @@ -1588,7 +1589,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body", "hyper", "ipnet", @@ -1685,12 +1686,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -1718,6 +1713,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +[[package]] +name = "impl-more" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" + [[package]] name = "indexmap" version = "2.14.0" @@ -1726,8 +1727,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", - "serde", - "serde_core", ] [[package]] @@ -1832,13 +1831,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1872,12 +1870,6 @@ dependencies = [ "spin", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libbz2-rs-sys" version = "0.2.5" @@ -1902,7 +1894,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "libc", "plain", "redox_syscall 0.8.1", @@ -1964,9 +1956,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" dependencies = [ "value-bag", ] @@ -1999,9 +1991,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -2037,6 +2029,76 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mtp" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "mtp-codec", + "mtp-common", + "mtp-host", + "mtp-transport", + "mtp-type-map", +] + +[[package]] +name = "mtp-codec" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "base64", + "byteorder", + "mtp-common", + "mtp-type-map", + "rand 0.8.6", +] + +[[package]] +name = "mtp-common" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "quinn", + "rustls", + "thiserror", + "wtransport", +] + +[[package]] +name = "mtp-host" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "mtp-codec", + "mtp-common", + "mtp-transport", + "rand 0.8.6", + "tokio", +] + +[[package]] +name = "mtp-transport" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "log", + "mtp-codec", + "mtp-common", + "rustls", + "rustls-native-certs", + "tokio", + "wtransport", +] + +[[package]] +name = "mtp-type-map" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "serde", + "serde_yaml", +] + [[package]] name = "nom" version = "7.1.3" @@ -2138,6 +2200,7 @@ dependencies = [ "hex", "hkdf", "json", + "mtp", "once_cell", "rand 0.8.6", "rand_core 0.6.4", @@ -2146,12 +2209,10 @@ dependencies = [ "rustls-pemfile", "sha2 0.10.9", "sqlx", - "strum 0.27.2", - "strum_macros 0.27.2", + "strum", + "strum_macros", "thiserror", "tokio", - "ttp-core", - "ttp-native", "uuid", "x448", "zip", @@ -2367,16 +2428,6 @@ 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.106" @@ -2388,9 +2439,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -2408,9 +2459,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "aws-lc-rs", "bytes", @@ -2444,9 +2495,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -2491,7 +2542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -2564,7 +2615,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", ] [[package]] @@ -2573,14 +2624,14 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2607,9 +2658,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -2621,8 +2672,8 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.14", - "http 1.4.1", + "h2 0.4.15", + "http 1.4.2", "http-body", "http-body-util", "hyper", @@ -2727,7 +2778,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2736,9 +2787,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "log", @@ -2862,7 +2913,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2940,6 +2991,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3040,9 +3104,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "serde", ] @@ -3189,7 +3253,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.12.1", + "bitflags 2.13.0", "byteorder", "bytes", "crc", @@ -3231,7 +3295,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.12.1", + "bitflags 2.13.0", "byteorder", "crc", "dotenvy", @@ -3307,12 +3371,6 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" - [[package]] name = "strum_macros" version = "0.27.2" @@ -3325,18 +3383,6 @@ dependencies = [ "syn", ] -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3345,9 +3391,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -3380,7 +3426,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3417,12 +3463,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -3432,15 +3477,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" dependencies = [ "num-conv", "time-core", @@ -3543,10 +3588,10 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "bytes", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body", "pin-project-lite", "tower", @@ -3605,33 +3650,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttp-core" -version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" -dependencies = [ - "base64", - "byteorder", - "rand 0.8.6", - "serde_json", - "strum 0.28.0", - "strum_macros 0.28.0", -] - -[[package]] -name = "ttp-native" -version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" -dependencies = [ - "quinn", - "rustls", - "rustls-native-certs", - "thiserror", - "tokio", - "ttp-core", - "wtransport", -] - [[package]] name = "typenum" version = "1.20.1" @@ -3687,6 +3705,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" @@ -3719,11 +3743,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -3779,20 +3803,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] @@ -3803,9 +3818,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3816,9 +3831,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3826,9 +3841,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3836,9 +3851,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -3849,52 +3864,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.12.1", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -3912,9 +3893,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] @@ -4217,100 +4198,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.12.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -4394,9 +4287,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4417,18 +4310,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -4458,18 +4351,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", @@ -4538,9 +4431,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 22ec471..5d4e97e 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } -ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } +mtp = { git = "https://git.methanium.net/methanium/mtp", features = ["host"] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" diff --git a/src/notifications/tauri.rs b/src/notifications/tauri.rs index c57c26d..8195988 100644 --- a/src/notifications/tauri.rs +++ b/src/notifications/tauri.rs @@ -2,12 +2,12 @@ use crate::log; use crate::util::file_util::load_file_vec; use crate::util::logger::PrintType; use dashmap::DashMap; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; use once_cell::sync::Lazy; +use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; use std::time::Duration; -use tokio::sync::futures; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; pub struct TauriConnection { pub user_id: i64, @@ -20,7 +20,8 @@ pub async fn start(port: u16) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = ttp_native::host( + let mut host: Host = host( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port, cert_pem, key_pem, @@ -59,45 +60,41 @@ async fn handle_connection(sender: Sender, receiver: &mut Receiver) { let sender = Arc::new(sender); while let Ok(cv) = receiver.receive().await { - match cv.get_type() { - CommunicationType::tauri_identification => { - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - if user_id != 0 { - current_user_id = user_id; - let conn = Arc::new(TauriConnection { - user_id, - sender: sender.clone(), - }); + if cv.is_type(CommunicationType::TauriIdentification) { + let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; + if user_id != 0 { + current_user_id = user_id; + let conn = Arc::new(TauriConnection { + user_id, + sender: sender.clone(), + }); - TAURI_CONNECTIONS - .entry(user_id) - .and_modify(|conns| { - conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); - conns.push(conn.clone()); - }) - .or_insert_with(|| vec![conn]); + TAURI_CONNECTIONS + .entry(user_id) + .and_modify(|conns| { + conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); + conns.push(conn.clone()); + }) + .or_insert_with(|| vec![conn]); - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - if let Err(e) = sender.send(&response).await { - log!( - PrintType::General, - "Failed to send tauri success response: {}", - e - ); - } else { - log!(user_id, PrintType::Client, "Tauri device registered"); - } - } - } - CommunicationType::ping => { let response = - CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); - if let Err(_) = sender.send(&response).await { - break; + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); + if let Err(e) = sender.send(&response).await { + log!( + PrintType::General, + "Failed to send tauri success response: {}", + e + ); + } else { + log!(user_id, PrintType::Client, "Tauri device registered"); } } - _ => {} + } else if cv.is_type(CommunicationType::Ping) { + let response = + CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); + if let Err(_) = sender.send(&response).await { + break; + } } } @@ -116,8 +113,8 @@ pub async fn send_notification(user_id: i64, sender_id: i64) { }; if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::push_notification) - .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + let cv = CommunicationValue::new(CommunicationType::PushNotification) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); let mut remove_needed = false; for conn in conns.iter() { @@ -141,8 +138,8 @@ pub async fn remove_notification(user_id: i64, sender_id: i64) { }; if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::read_notification) - .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + let cv = CommunicationValue::new(CommunicationType::ReadNotification) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); let mut remove_needed = false; for conn in conns.iter() { diff --git a/src/server/server.rs b/src/server/server.rs index 3c7fc05..c367679 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -28,7 +28,7 @@ pub async fn start(port: u16) -> anyhow::Result<()> { .with_no_client_auth() .with_single_cert(cert_chain, key)?; - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + config.alpn_protocols = vec![b"h2".to_vec(), b"hmtp/1.1".to_vec()]; let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); let addr = format!("{}:{}", bind_addr, port); diff --git a/src/server/short_link.rs b/src/server/short_link.rs index c0ab51d..b3f91bd 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -11,7 +11,7 @@ pub async fn add_short_link(long: &str) -> Result { LINKS.insert(raw.clone(), long.to_string()); Ok(format!( - "https://omega.tensamin.net/direct/{}", + "hmtps://omega.tensamin.net/direct/{}", format_with_dashes(&raw) )) } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 93804fd..cc53520 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -11,6 +11,9 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; +use std::net::{IpAddr, Ipv4Addr}; use rand::{Rng, distributions::Alphanumeric}; use std::{ sync::Arc, @@ -20,8 +23,6 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; use x448::PublicKey; // ============================================================================ @@ -92,7 +93,7 @@ impl AuthState { } // ============================================================================ -// Omikron Connection (ttp/QUIC-based) +// Omikron Connection (mtp/QUIC-based) // ============================================================================ pub struct OmikronConnection { @@ -180,7 +181,7 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_in!(PrintType::Omikron, &cv); } @@ -193,7 +194,7 @@ impl OmikronConnection { } // Handle ping regardless of auth state - if cv.is_type(CommunicationType::ping) { + if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } @@ -213,22 +214,22 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::identification) { + if !cv.is_type(CommunicationType::Identification) { let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) .await; return Err(OmikronError::NotAuthenticated); } // Extract omikron ID let omikron_id = cv - .get_data(DataTypes::omikron_id) + .get_data(DataType::OmikronId) .as_number() .ok_or(OmikronError::InvalidResponse)?; log!("Omikron {:?} connected", omikron_id); // Lookup omikron in database - let (public_key, _) = get_omikron_by_id(omikron_id) + let (public_key, _) = get_omikron_by_id(omikron_id as i64) .await .map_err(|e| OmikronError::Sql(e.to_string()))?; @@ -261,7 +262,9 @@ impl OmikronConnection { log!("Stored Pubkey"); - *self.state.write().await = AuthState::Identified { omikron_id }; + *self.state.write().await = AuthState::Identified { + omikron_id: omikron_id as i64, + }; log!("Stored State"); @@ -279,28 +282,28 @@ impl OmikronConnection { log!("Encrypted Challenge"); // Send challenge response - let response = CommunicationValue::new(CommunicationType::challenge) + let response = CommunicationValue::new(CommunicationType::Challenge) .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, + .add_typed_default( + DataType::PublicKey, DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + .add_typed_default(DataType::Content, DataValue::Str(encrypted)); log!("Sending Challenge"); self.send(&response).await } async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::challenge_response) { + if !cv.is_type(CommunicationType::ChallengeResponse) { let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) .await; return Err(OmikronError::NotAuthenticated); } let client_response = cv - .get_data(DataTypes::challenge) + .get_data(DataType::Content) .as_str() .ok_or(OmikronError::InvalidResponse)?; @@ -312,16 +315,16 @@ impl OmikronConnection { omikron_manager::add_omikron(self.clone()).await; - let response = CommunicationValue::new(CommunicationType::identification_response) + let response = CommunicationValue::new(CommunicationType::IdentificationResponse) .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::Bool(true)); + .add_typed_default(DataType::Accepted, DataValue::Bool(true)); self.clone().send(&response).await?; log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + .send_error_response(cv.get_id(), CommunicationType::ErrorInvalidChallenge) .await; Err(OmikronError::AuthenticationFailed) } @@ -336,52 +339,53 @@ impl OmikronConnection { cv: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { - match cv.get_type() { + let comm_type = cv.get_comm_type_enum(); + match comm_type { // Link shortening - CommunicationType::shorten_link => self.handle_shorten_link(cv).await, + Some(CommunicationType::ShortenLink) => self.handle_shorten_link(cv).await, // Online status tracking - CommunicationType::user_connected => { + Some(CommunicationType::UserConnected) => { self.handle_user_connected(cv, omikron_id).await; Ok(()) } - CommunicationType::user_disconnected => { + Some(CommunicationType::UserDisconnected) => { self.handle_user_disconnected(cv, omikron_id).await; Ok(()) } - CommunicationType::iota_connected => { + Some(CommunicationType::IotaConnected) => { self.handle_iota_connected(cv, omikron_id).await; Ok(()) } - CommunicationType::iota_disconnected => { + Some(CommunicationType::IotaDisconnected) => { self.handle_iota_disconnected(cv, omikron_id).await; Ok(()) } - CommunicationType::sync_client_iota_status => { + Some(CommunicationType::SyncClientIotaStatus) => { self.handle_sync_status(cv, omikron_id).await; Ok(()) } - CommunicationType::get_user_data => self.handle_get_user_data(cv).await, - CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, + Some(CommunicationType::GetUserData) => self.handle_get_user_data(cv).await, + Some(CommunicationType::GetIotaData) => self.handle_get_iota_data(cv).await, - CommunicationType::get_register => self.handle_get_register(cv).await, - CommunicationType::complete_register_iota => { + Some(CommunicationType::GetRegister) => self.handle_get_register(cv).await, + Some(CommunicationType::CompleteRegisterIota) => { self.handle_complete_register_iota(cv).await } - CommunicationType::complete_register_user => { + Some(CommunicationType::CompleteRegisterUser) => { self.handle_complete_register_user(cv).await } - CommunicationType::change_user_data => self.handle_change_user_data(cv).await, - CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, - CommunicationType::delete_user => self.handle_delete_user(cv).await, - CommunicationType::delete_iota => self.handle_delete_iota(cv).await, + Some(CommunicationType::ChangeUserData) => self.handle_change_user_data(cv).await, + Some(CommunicationType::ChangeIotaData) => self.handle_change_iota_data(cv).await, + Some(CommunicationType::DeleteUser) => self.handle_delete_user(cv).await, + Some(CommunicationType::DeleteIota) => self.handle_delete_iota(cv).await, - CommunicationType::get_notifications => self.handle_get_notifications(cv).await, - CommunicationType::read_notification => self.handle_read_notification(cv).await, - CommunicationType::push_notification => self.handle_push_notification(cv).await, - CommunicationType::get_states => self.handle_get_states(cv).await, + Some(CommunicationType::GetNotifications) => self.handle_get_notifications(cv).await, + Some(CommunicationType::ReadNotification) => self.handle_read_notification(cv).await, + Some(CommunicationType::PushNotification) => self.handle_push_notification(cv).await, + Some(CommunicationType::GetStates) => self.handle_get_states(cv).await, _ => { log_err!( @@ -401,7 +405,7 @@ impl OmikronConnection { async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let link = cv - .get_data(DataTypes::link) + .get_data(DataType::Link) .as_str() .ok_or(OmikronError::InvalidResponse)?; @@ -409,32 +413,28 @@ impl OmikronConnection { .await .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; - let response = CommunicationValue::new(CommunicationType::shorten_link) + let response = CommunicationValue::new(CommunicationType::ShortenLink) .with_id(cv.get_id()) - .add_data(DataTypes::link, DataValue::Str(short)); + .add_typed_default(DataType::Link, DataValue::Str(short)); self.send(&response).await } async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { let status = cv - .get_data(DataTypes::user_state) + .get_data(DataType::UserState) .as_str() .and_then(|s| UserStatus::from_str(s)) .unwrap_or(UserStatus::user_online); - user_online_tracker::track_user_status( - user_id.try_into().unwrap(), - status, - omikron_id, - ); + user_online_tracker::track_user_status(user_id.try_into().unwrap(), status, omikron_id); } } async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { user_online_tracker::track_user_status( user_id as i64, @@ -447,7 +447,7 @@ impl OmikronConnection { async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); - let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = match cv.get_data(DataType::IotaId).as_number() { Some(id) => id as i64, None => return, }; @@ -456,7 +456,7 @@ impl OmikronConnection { let mut user_ids = Vec::new(); if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id.try_into().unwrap())); + user_ids.push(DataValue::SignedNumber(user_id.try_into().unwrap())); user_online_tracker::track_user_status( user_id.try_into().unwrap(), UserStatus::user_offline, @@ -467,16 +467,16 @@ impl OmikronConnection { log_in!(PrintType::General, "SQL error loading users for IOTA"); } - let response = CommunicationValue::new(CommunicationType::iota_user_data) + let response = CommunicationValue::new(CommunicationType::IotaUserData) .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); let _ = self.send(&response).await; } async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); - let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = match cv.get_data(DataType::IotaId).as_number() { Some(id) => id as i64, None => return, }; @@ -490,11 +490,11 @@ impl OmikronConnection { } async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { - if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { + if let DataValue::Array(user_ids) = cv.get_data(DataType::UserIds) { for user_id_val in user_ids { - if let DataValue::Number(user_id) = user_id_val { + if let DataValue::SignedNumber(user_id) = user_id_val { user_online_tracker::track_user_status( - *user_id, + *user_id as i64, UserStatus::user_offline, omikron_id, ); @@ -502,10 +502,10 @@ impl OmikronConnection { } } - if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { + if let DataValue::Array(iota_ids) = cv.get_data(DataType::IotaIds) { for iota_id_val in iota_ids { - if let DataValue::Number(iota_id) = iota_id_val { - user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); + if let DataValue::SignedNumber(iota_id) = iota_id_val { + user_online_tracker::track_iota_connection(*iota_id as i64, omikron_id, true); } } } @@ -513,7 +513,7 @@ impl OmikronConnection { async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by user_id first - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok(user_data) = get_by_user_id(user_id as i64).await { let response = self .clone() @@ -524,7 +524,7 @@ impl OmikronConnection { } // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok(user_data) = get_by_username(username).await { let response = self .clone() @@ -536,7 +536,7 @@ impl OmikronConnection { // Not found let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); self.send(&response).await } @@ -573,28 +573,28 @@ impl OmikronConnection { _, ) = user; - let mut response = CommunicationValue::new(CommunicationType::get_user_data) + let mut response = CommunicationValue::new(CommunicationType::GetUserData) .with_id(msg_id) - .add_data(DataTypes::username, DataValue::Str(username.clone())) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::user_id, DataValue::Number(id)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) - .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) - .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); + .add_typed_default(DataType::Username, DataValue::Str(username.clone())) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::SubLevel, DataValue::SignedNumber(sub_level as i128)) + .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); // Display name (fallback to username) let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); - response = response.add_data(DataTypes::display, DataValue::Str(display_name)); + response = response.add_typed_default(DataType::Display, DataValue::Str(display_name)); // Optional fields if let Some(s) = status.filter(|s| !s.is_empty()) { - response = response.add_data(DataTypes::status, DataValue::Str(s)); + response = response.add_typed_default(DataType::Status, DataValue::Str(s)); } if let Some(a) = about.filter(|a| !a.is_empty()) { - response = response.add_data(DataTypes::about, DataValue::Str(a)); + response = response.add_typed_default(DataType::About, DataValue::Str(a)); } if let Some(av) = avatar { - response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); + response = response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); } // Online status @@ -608,24 +608,24 @@ impl OmikronConnection { } else { us.connection_type.clone() }; - response = response.add_data( - DataTypes::online_status, + response = response.add_typed_default( + DataType::OnlineStatus, DataValue::Str(display_status.to_string()), ); - response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); + response = response.add_typed_default(DataType::OmikronId, DataValue::SignedNumber(us.omikron_id.into())); } else { - response = response.add_data( - DataTypes::online_status, + response = response.add_typed_default( + DataType::OnlineStatus, DataValue::Str(UserStatus::iota_offline.to_string()), ); } - response = response.add_data( - DataTypes::omikron_connections, + response = response.add_typed_default( + DataType::OmikronConnections, DataValue::Array( iota_connections .into_iter() - .map(DataValue::Number) + .map(|id| DataValue::SignedNumber(id as i128)) .collect(), ), ); @@ -635,7 +635,7 @@ impl OmikronConnection { async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { let response = self .clone() @@ -646,7 +646,7 @@ impl OmikronConnection { } // Try by user_id - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(user_id as i64).await { @@ -667,7 +667,7 @@ impl OmikronConnection { } // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_username(username).await { @@ -688,7 +688,7 @@ impl OmikronConnection { } let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); self.send(&response).await } @@ -700,27 +700,27 @@ impl OmikronConnection { user_id: Option, username: Option, ) -> CommunicationValue { - let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + let mut response = CommunicationValue::new(CommunicationType::GetIotaData) .with_id(msg_id) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); if let Some(uid) = user_id { - response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); + response = response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); } if let Some(uname) = username { - response = response.add_data(DataTypes::username, DataValue::Str(uname)); + response = response.add_typed_default(DataType::Username, DataValue::Str(uname)); } let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - response.add_data( - DataTypes::omikron_connections, + response.add_typed_default( + DataType::OmikronConnections, DataValue::Array( iota_connections .into_iter() - .map(DataValue::Number) + .map(|id| DataValue::SignedNumber(id as i128)) .collect(), ), ) @@ -728,9 +728,9 @@ impl OmikronConnection { async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::get_register) + let response = CommunicationValue::new(CommunicationType::GetRegister) .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); + .add_typed_default(DataType::UserId, DataValue::SignedNumber(register_id as i128)); self.send(&response).await } @@ -738,24 +738,21 @@ impl OmikronConnection { self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let iota_id_opt = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); + let iota_id_opt = cv.get_data(DataType::IotaId).as_number().map(|n| n as i64); - if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { + if let Some(public_key) = cv.get_data(DataType::PublicKey).as_str() { if let Some(iota_id) = iota_id_opt { // Register existing IOTA match sql::register_complete_iota(iota_id, public_key.to_string()).await { Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) + let response = CommunicationValue::new(CommunicationType::Success) .with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } @@ -764,21 +761,21 @@ impl OmikronConnection { match sql::create_new_iota(public_key.to_string()).await { Ok(new_iota_id) => { let response = - CommunicationValue::new(CommunicationType::complete_register_iota) + CommunicationValue::new(CommunicationType::CompleteRegisterIota) .with_id(cv.get_id()) - .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(new_iota_id.into())); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) .await } } @@ -787,21 +784,18 @@ impl OmikronConnection { self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let user_id = cv - .get_data(DataTypes::user_id) - .as_number() - .map(|n| n as i64); + let user_id = cv.get_data(DataType::UserId).as_number().map(|n| n as i64); let username = cv - .get_data(DataTypes::username) + .get_data(DataType::Username) .as_str() .map(|s| s.to_string()); let public_key = cv - .get_data(DataTypes::public_key) + .get_data(DataType::PublicKey) .as_str() .map(|s| s.to_string()); let iota_id = cv.get_sender(); let reset_token = cv - .get_data(DataTypes::reset_token) + .get_data(DataType::ResetToken) .as_str() .map(|s| s.to_string()); @@ -811,18 +805,18 @@ impl OmikronConnection { match sql::register_complete_user(uid, uname, pk, iota_id as i64, rt).await { Ok(_) => { let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) .await } } @@ -833,39 +827,39 @@ impl OmikronConnection { let mut error_message = String::new(); // Process each field - if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Err(e) = sql::change_username(user_id, username.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(display) = cv.get_data(DataTypes::display).as_str() { + if let Some(display) = cv.get_data(DataType::Display).as_str() { if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { + if let Some(avatar) = cv.get_data(DataType::Avatar).as_str() { if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(about) = cv.get_data(DataTypes::about).as_str() { + if let Some(about) = cv.get_data(DataType::About).as_str() { if let Err(e) = sql::change_about(user_id, about.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(status) = cv.get_data(DataTypes::status).as_str() { + if let Some(status) = cv.get_data(DataType::Status).as_str() { if let Err(e) = sql::change_status(user_id, status.to_string()).await { success = false; error_message = e.to_string(); } } if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataTypes::public_key).as_str(), - cv.get_data(DataTypes::private_key_hash).as_str(), + cv.get_data(DataType::PublicKey).as_str(), + cv.get_data(DataType::PrivateKeyHash).as_str(), ) { if let Err(e) = sql::change_keys( user_id, @@ -880,12 +874,12 @@ impl OmikronConnection { } if success { - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); + .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); self.send(&response).await } } @@ -895,8 +889,8 @@ impl OmikronConnection { if let (iota_id, Some(reset_token), Some(new_token)) = ( cv.get_sender(), - cv.get_data(DataTypes::reset_token).as_str(), - cv.get_data(DataTypes::new_token).as_str(), + cv.get_data(DataType::ResetToken).as_str(), + cv.get_data(DataType::NewToken).as_str(), ) { match sql::get_by_user_id(user_id).await { Ok(user) => { @@ -918,31 +912,31 @@ impl OmikronConnection { } if success { - let response = CommunicationValue::new(CommunicationType::success) + let response = CommunicationValue::new(CommunicationType::Success) .with_id(cv.get_id()); self.send(&response).await } else { let response = - CommunicationValue::new(CommunicationType::error_internal) + CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); + .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); self.send(&response).await } } else { self.send_error_response( cv.get_id(), - CommunicationType::error_invalid_challenge, + CommunicationType::ErrorInvalidChallenge, ) .await } } Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error_not_found) + self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound) .await } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) .await } } @@ -952,13 +946,13 @@ impl OmikronConnection { match sql::delete_user(user_id).await { Ok(_) => { let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } @@ -969,13 +963,13 @@ impl OmikronConnection { match sql::delete_iota(iota_id as i64).await { Ok(_) => { let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } @@ -990,9 +984,10 @@ impl OmikronConnection { Ok(notifications) => notifications .into_iter() .map(|(sender, amount)| { + let tm = mtp::type_map::TypeMap::latest(); DataValue::Container(vec![ - (DataTypes::sender_id, DataValue::Number(sender)), - (DataTypes::amount, DataValue::Number(amount)), + (DataType::SenderId.to_id(&tm), DataValue::SignedNumber(sender.into())), + (DataType::Amount.to_id(&tm), DataValue::SignedNumber(amount.into())), ]) }) .collect(), @@ -1002,9 +997,9 @@ impl OmikronConnection { } }; - let response = CommunicationValue::new(CommunicationType::get_notifications) + let response = CommunicationValue::new(CommunicationType::GetNotifications) .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(response_array)); + .add_typed_default(DataType::Notifications, DataValue::Array(response_array)); self.send(&response).await } @@ -1014,21 +1009,21 @@ impl OmikronConnection { ) -> OmikronResult<()> { let receiver_id = match cv.get_sender() { s if s > 0 => s as i64, - _ => match cv.get_data(DataTypes::receiver_id).as_number() { + _ => match cv.get_data(DataType::ReceiverId).as_number() { Some(id) => id as i64, None => return Ok(()), }, }; if let Some(other_id) = cv - .get_data(DataTypes::sender_id) + .get_data(DataType::SenderId) .as_number() .map(|n| n as i64) { if let Err(e) = sql::read_notification(receiver_id, other_id).await { log!(PrintType::General, "SQL read_notification error: {}", e); } else { - let response = CommunicationValue::new(CommunicationType::read_notification) + let response = CommunicationValue::new(CommunicationType::ReadNotification) .with_id(cv.get_id()); let _ = self.send(&response).await; @@ -1036,9 +1031,9 @@ impl OmikronConnection { crate::notifications::tauri::remove_notification(receiver_id, other_id).await; // Sync with other Omikron clients - let sync_cv = CommunicationValue::new(CommunicationType::read_notification) + let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) - .add_data(DataTypes::sender_id, DataValue::Number(other_id)); + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(other_id.into())); crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; } } @@ -1051,13 +1046,13 @@ impl OmikronConnection { ) -> OmikronResult<()> { let receiver_id = match cv.get_receiver() { r if r > 0 => r as i64, - _ => match cv.get_data(DataTypes::receiver_id).as_number() { + _ => match cv.get_data(DataType::ReceiverId).as_number() { Some(id) => id as i64, None => return Ok(()), }, }; - let sender_id = match cv.get_data(DataTypes::sender_id).as_number() { + let sender_id = match cv.get_data(DataType::SenderId).as_number() { Some(id) => id as i64, None => cv.get_sender() as i64, }; @@ -1066,31 +1061,31 @@ impl OmikronConnection { log!(PrintType::General, "SQL add_notification error: {}", e); } else { let response = - CommunicationValue::new(CommunicationType::push_notification).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); let _ = self.send(&response).await; // Sync with Tauri crate::notifications::tauri::send_notification(receiver_id, sender_id).await; // Sync with other Omikron clients - let push_cv = CommunicationValue::new(CommunicationType::push_notification) + let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) - .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id.into())); crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; } Ok(()) } async fn handle_get_states(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_ids = match cv.get_data(DataTypes::user_ids) { + let user_ids = match cv.get_data(DataType::UserIds) { DataValue::Array(ids) => ids, _ => return Ok(()), }; let mut states = Vec::new(); for id_val in user_ids { - if let DataValue::Number(user_id) = id_val { - let user_id = *user_id; + if let DataValue::SignedNumber(user_id) = id_val { + let user_id = *user_id as i64; let status = user_online_tracker::get_user_status(user_id); let status_str = match status { Some(ref us) => { @@ -1102,25 +1097,26 @@ impl OmikronConnection { } None => UserStatus::iota_offline.to_string(), }; + let tm = mtp::type_map::TypeMap::latest(); let mut map = Vec::new(); - map.push((DataTypes::user_id, DataValue::Number(user_id))); - map.push((DataTypes::user_state, DataValue::Str(status_str))); + map.push((DataType::UserId.to_id(&tm), DataValue::SignedNumber(user_id.into()))); + map.push((DataType::UserState.to_id(&tm), DataValue::Str(status_str))); states.push(DataValue::Container(map)); } } - let response = CommunicationValue::new(CommunicationType::get_states) + let response = CommunicationValue::new(CommunicationType::GetStates) .with_id(cv.get_id()) - .add_data(DataTypes::user_states, DataValue::Array(states)); + .add_typed_default(DataType::UserStates, DataValue::Array(states)); self.send(&response).await } async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - *self.ping.write().await = *last_ping; + if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { + *self.ping.write().await = *last_ping as i64; } - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + let response = CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); self.send(&response).await } @@ -1129,7 +1125,7 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_out!(PrintType::Omikron, cv); } @@ -1196,7 +1192,8 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = ttp_native::host( + let mut host: Host = host( + IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, cert_pem, key_pem, diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 70c73cd..f98a75c 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,9 +1,9 @@ use crate::transport::omikron_connection::OmikronConnection; use dashmap::DashMap; +use mtp::codec::CommunicationValue; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; use std::sync::Arc; -use ttp_core::CommunicationValue; pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); diff --git a/src/util/logger.rs b/src/util/logger.rs index 845489f..31606b6 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,4 @@ use std::{ - collections::BTreeMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -9,7 +8,7 @@ use std::{ }; use ansi_term::Color; -use ttp_core::{CommunicationValue, DataTypes, DataValue}; +use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version}; use crate::util::file_util::get_directory; @@ -248,17 +247,19 @@ pub fn format_cv(cv: &CommunicationValue) -> String { let comm_type = cv.get_type().to_string(); parts.push(format!("{}", comm_type)); - let data: &BTreeMap = cv.get_data_container(); + let data = cv.data(); - let formated_data = - format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); + let formated_data = format_data_container( + data.iter().map(|(k, v)| (*k, v.clone())).collect(), + Version(1, 0), + ); parts.push(format!("{}", formated_data)); parts.join(": ") } -fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { +fn format_data_container(data: Vec<(DataTypeId, DataValue)>, version: Version) -> String { let parts: Vec = data .into_iter() .map(|(key, value)| { @@ -268,12 +269,13 @@ fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); + let inner_formatted = + format_data_container(inner, version.clone()); format!("{}={{ {} }}", key_str, inner_formatted) } DataValue::Array(arr) => { - let arr_formatted = format_array(arr); + let arr_formatted = format_array(arr, version.clone()); format!("{}=[{}]", key_str, arr_formatted) } @@ -282,7 +284,7 @@ fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { DataValue::BoolTrue => format!("{}=true", key_str), DataValue::BoolFalse => format!("{}=false", key_str), - DataValue::Number(num) => format!("{}={}", key_str, num), + DataValue::SignedNumber(num) => format!("{}={}", key_str, num), _ => "".to_string(), } @@ -292,19 +294,19 @@ fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { parts.join(", ") } -fn format_array(arr: Vec) -> String { +fn format_array(arr: Vec, version: Version) -> String { let parts: Vec = arr .into_iter() .map(|value| match value { DataValue::Str(s) => format!("\"{}\"", s), DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); + let inner_formatted = format_data_container(inner, version.clone()); format!("{{ {} }}", inner_formatted) } DataValue::Array(inner_arr) => { - let formatted = format_array(inner_arr); + let formatted = format_array(inner_arr, version.clone()); format!("[{}]", formatted) } @@ -313,7 +315,7 @@ fn format_array(arr: Vec) -> String { DataValue::BoolTrue => "true".to_string(), DataValue::BoolFalse => "false".to_string(), - DataValue::Number(num) => num.to_string(), + DataValue::SignedNumber(num) => num.to_string(), _ => String::new(), }) diff --git a/type-maps.yaml b/type-maps.yaml new file mode 100644 index 0000000..73a15e3 --- /dev/null +++ b/type-maps.yaml @@ -0,0 +1,240 @@ +# The version a Client should use +protocol_version: "1.0" + +# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended +# Fixed CommunicationType markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorBadVersion: 2 +# Disconnect: 3 +# Redirect: 4 +# Shutdown: 5 +# BadRequest: 6 +# Unauthorized: 7 +# Forbidden: 8 +# NotFound: 9 +# TooManyRequests: 10 +# InternalServerError: 11 +# BadGateway: 12 +# ServiceUnavailable: 13 +# GatewayTimeout: 14 +# Identification: 15 +# IdentificationResponse: 16 +# Register: 17 +# RegisterResponse: 18 +# Ping: 19 +# Pong: 20 +# +# Fixed Data Type markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorMessage: 2 +# Version: 3 +# Description: 4 +# Timestamp: 5 +# Id: 6 +# ClientNonce: 7 +# ServerNonce: 8 +# PublicKeys: 9 +# Signature: 10 +# Connected: 11 +# +# If a Type can't be used it will be mapped to 0 + +type_maps: + "1.0": # Protocol version 1.0 + CommunicationTypes: + ErrorProtocol: 33 + ErrorAnonymous: 34 + ErrorInternal: 35 + ErrorInvalidData: 36 + ErrorInvalidUserId: 37 + ErrorInvalidOmikronId: 38 + ErrorNotFound: 39 + ErrorNotAuthenticated: 40 + ErrorNoIota: 41 + ErrorInvalidChallenge: 42 + ErrorInvalidSecret: 43 + ErrorInvalidPrivateKey: 44 + ErrorInvalidPublicKey: 45 + ErrorNoUserId: 46 + ErrorNoCallId: 47 + ErrorInvalidCallId: 48 + Success: 49 + ShortenLink: 50 + SettingsSave: 51 + SettingsLoad: 52 + SettingsList: 53 + GlobalSettingsSave: 54 + GlobalSettingsLoad: 55 + Message: 56 + MessageState: 57 + MessageSend: 58 + MessageLive: 59 + MessageOtherIota: 60 + MessageChunk: 61 + MessagesGet: 62 + PushNotification: 63 + ReadNotification: 64 + GetNotifications: 65 + TauriIdentification: 66 + ChangeConfirm: 67 + ConfirmReceive: 68 + ConfirmRead: 69 + GetChats: 70 + GetStates: 71 + AddCommunity: 72 + RemoveCommunity: 73 + GetCommunities: 74 + RegisterIota: 81 + RegisterIotaSuccess: 82 + AddConversation: 85 + SendChat: 86 + ClientChanged: 87 + ClientConnected: 88 + ClientDisconnected: 89 + ClientClosed: 90 + PublicKey: 91 + PrivateKey: 92 + WebrtcSdp: 93 + WebrtcIce: 94 + StartStream: 95 + EndStream: 96 + WatchStream: 97 + CallToken: 98 + CallInvite: 99 + CallDisconnectUser: 100 + CallTimeoutUser: 101 + CallSetAnonymousJoining: 102 + CallData: 103 + EndCall: 104 + Function: 105 + Update: 106 + CreateUser: 107 + RhoUpdate: 108 + UserConnected: 109 + UserDisconnected: 110 + IotaConnected: 111 + IotaDisconnected: 112 + SyncClientIotaStatus: 113 + GetUserData: 114 + GetIotaData: 115 + IotaUserData: 116 + ChangeUserData: 117 + ChangeIotaData: 118 + GetRegister: 119 + CompleteRegisterUser: 120 + CompleteRegisterIota: 121 + DeleteUser: 122 + DeleteIota: 123 + StartRegister: 124 + CompleteRegister: 125 + GetApp: 126 + CreateApp: 127 + DeleteApp: 128 + SaveAppData: 129 + LoadAppData: 130 + AppIdentification: 131 + AppChallenge: 132 + AppChallengeResponse: 133 + AppIdentificationResponse: 134 + LoadTxtRecord: 135 + DataTypes: + ErrorType: 32 + ErrorProtocol: 33 + AcceptedIds: 34 + Uuid: 35 + RegisterId: 36 + Link: 37 + Settings: 38 + SettingsName: 39 + ChatPartnerId: 40 + ChatPartnerName: 41 + IotaId: 42 + UserId: 43 + UserIds: 44 + IotaIds: 45 + UserState: 46 + UserStates: 47 + UserPings: 48 + CallState: 49 + ScreenShare: 50 + PrivateKeyHash: 51 + Accepted: 52 + AcceptedProfiles: 53 + DeniedProfiles: 54 + Content: 55 + Messages: 56 + Notifications: 57 + SendTime: 58 + GetTime: 59 + GetVariant: 60 + SharedSecretOwn: 61 + SharedSecretOther: 62 + SharedSecretSign: 63 + SharedSecret: 64 + CallId: 65 + CallToken: 66 + CallSecret: 67 + Untill: 68 + Enabled: 69 + StartDate: 70 + EndDate: 71 + ReceiverId: 72 + SenderId: 73 + Signed: 75 + Message: 76 + MessageState: 77 + LastPing: 78 + PingIota: 79 + PingClients: 80 + Matches: 81 + Omikron: 82 + Offset: 83 + Amount: 84 + Position: 85 + Name: 86 + Path: 87 + Codec: 88 + Function: 89 + Payload: 90 + Result: 91 + Interactables: 92 + WantToWatch: 93 + Watcher: 94 + CreatedAt: 95 + Username: 96 + Display: 97 + Avatar: 98 + About: 99 + Status: 100 + PublicKey: 101 + SubLevel: 102 + SubEnd: 103 + CommunityAddress: 104 + CommunityTitle: 106 + Communities: 107 + RhoConnections: 108 + User: 109 + OnlineStatus: 110 + OmikronId: 111 + OmikronConnections: 112 + ResetToken: 113 + NewToken: 114 + CallInvited: 115 + CallMembers: 116 + Calls: 117 + Timeout: 118 + HasAdmin: 119 + LastMessageAt: 120 + Height: 121 + SentBySelf: 122 + SessionId: 123 + Contacts: 124 + LastMessage: 125 + AppIdentifier: 127 + AppPrivateKey: 128 + AppPublicKey: 129 + AppSession: 130 + AppData: 131 + TauriToken: 132 From da86bdaf5a648d481f354246e2acfd99a38e7056 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 28 Jun 2026 13:51:17 +0200 Subject: [PATCH 158/220] Initial migration to MTP [Broken] --- .cargo/config.toml | 2 + Cargo.lock | 551 +++++++++++----------------- Cargo.toml | 3 +- src/notifications/tauri.rs | 81 ++-- src/server/server.rs | 2 +- src/server/short_link.rs | 2 +- src/transport/omikron_connection.rs | 341 +++++++++-------- src/transport/omikron_manager.rs | 2 +- src/util/logger.rs | 28 +- type-maps.yaml | 240 ++++++++++++ 10 files changed, 691 insertions(+), 561 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 type-maps.yaml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..d363b83 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[env] +MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true } diff --git a/Cargo.lock b/Cargo.lock index 5f83697..1f85cb3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "bytes", "futures-core", "futures-sink", @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "actix-http" -version = "3.12.1" +version = "3.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93acb4a42f64936f9b8cae4a433b237599dd6eb6ed06124eb67132ef8cc90662" +checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" dependencies = [ "actix-codec", "actix-rt", @@ -31,14 +31,14 @@ dependencies = [ "actix-tls", "actix-utils", "base64", - "bitflags 2.12.1", + "bitflags 2.13.0", "brotli", "bytes", "bytestring", "derive_more", "encoding_rs", "flate2", - "foldhash", + "foldhash 0.2.0", "futures-core", "h2 0.3.27", "http 0.2.12", @@ -131,7 +131,7 @@ dependencies = [ "actix-service", "actix-utils", "futures-core", - "impl-more", + "impl-more 0.1.9", "pin-project-lite", "rustls-pki-types", "tokio", @@ -152,9 +152,9 @@ dependencies = [ [[package]] name = "actix-web" -version = "4.13.0" +version = "4.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff87453bc3b56e9b2b23c1cc0b1be8797184accf51d2abe0f8a33ec275d316bf" +checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" dependencies = [ "actix-codec", "actix-http", @@ -172,10 +172,10 @@ dependencies = [ "cookie", "derive_more", "encoding_rs", - "foldhash", + "foldhash 0.2.0", "futures-core", "futures-util", - "impl-more", + "impl-more 0.3.1", "itoa", "language-tags", "log", @@ -264,9 +264,9 @@ checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" dependencies = [ "alloc-no-stdlib", ] @@ -288,9 +288,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.102" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" [[package]] name = "arbitrary" @@ -555,9 +555,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84d7ced0ae9557296835c32bf1b1e02b44c746701f898460fb000d7eaa84f00a" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" dependencies = [ "serde_core", ] @@ -573,9 +573,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] @@ -595,9 +595,9 @@ dependencies = [ [[package]] name = "brotli" -version = "8.0.3" +version = "8.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8119e4516436f5708bbc474a9d395bf12f1b5395e93a92a56e647ac3388c8610" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -606,9 +606,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "5.0.1" +version = "5.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5962523e1b92ce1b5e793d9169b9943eece10d39f62550bc04bb605d75b94924" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -628,9 +628,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" [[package]] name = "bytestring" @@ -652,9 +652,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.63" +version = "1.2.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" dependencies = [ "find-msvc-tools", "jobserver", @@ -676,9 +676,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -929,9 +929,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "derive_arbitrary" @@ -985,7 +982,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", + "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", ] @@ -1163,6 +1160,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1316,16 +1319,14 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "rand_core 0.10.1", - "wasip2", - "wasip3", ] [[package]] @@ -1371,16 +1372,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.4.1", + "http 1.4.2", "indexmap", "slab", "tokio", @@ -1402,7 +1403,7 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", ] [[package]] @@ -1490,9 +1491,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1505,7 +1506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.1", + "http 1.4.2", ] [[package]] @@ -1516,7 +1517,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.1", + "http 1.4.2", "http-body", "pin-project-lite", ] @@ -1535,9 +1536,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9155a582abd142abc056962c29e3ce5ff2ad5469f4246b537ed42c5deba857da" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ "typenum", ] @@ -1552,8 +1553,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.14", - "http 1.4.1", + "h2 0.4.15", + "http 1.4.2", "http-body", "httparse", "itoa", @@ -1569,7 +1570,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.1", + "http 1.4.2", "hyper", "hyper-util", "rustls", @@ -1588,7 +1589,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body", "hyper", "ipnet", @@ -1685,12 +1686,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - [[package]] name = "idna" version = "1.1.0" @@ -1718,6 +1713,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" +[[package]] +name = "impl-more" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" + [[package]] name = "indexmap" version = "2.14.0" @@ -1726,8 +1727,6 @@ checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.1", - "serde", - "serde_core", ] [[package]] @@ -1832,13 +1831,12 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -1872,12 +1870,6 @@ dependencies = [ "spin", ] -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libbz2-rs-sys" version = "0.2.5" @@ -1902,7 +1894,7 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "libc", "plain", "redox_syscall 0.8.1", @@ -1964,9 +1956,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.31" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "113b30b4cd05f7c06868fdb2854f66a7b9fece9a48425351cd532e810d74024f" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" dependencies = [ "value-bag", ] @@ -1999,9 +1991,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.1" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -2037,6 +2029,76 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "mtp" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "mtp-codec", + "mtp-common", + "mtp-host", + "mtp-transport", + "mtp-type-map", +] + +[[package]] +name = "mtp-codec" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "base64", + "byteorder", + "mtp-common", + "mtp-type-map", + "rand 0.8.6", +] + +[[package]] +name = "mtp-common" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "quinn", + "rustls", + "thiserror", + "wtransport", +] + +[[package]] +name = "mtp-host" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "mtp-codec", + "mtp-common", + "mtp-transport", + "rand 0.8.6", + "tokio", +] + +[[package]] +name = "mtp-transport" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "log", + "mtp-codec", + "mtp-common", + "rustls", + "rustls-native-certs", + "tokio", + "wtransport", +] + +[[package]] +name = "mtp-type-map" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "serde", + "serde_yaml", +] + [[package]] name = "nom" version = "7.1.3" @@ -2138,6 +2200,7 @@ dependencies = [ "hex", "hkdf", "json", + "mtp", "once_cell", "rand 0.8.6", "rand_core 0.6.4", @@ -2146,12 +2209,10 @@ dependencies = [ "rustls-pemfile", "sha2 0.10.9", "sqlx", - "strum 0.27.2", - "strum_macros 0.27.2", + "strum", + "strum_macros", "thiserror", "tokio", - "ttp-core", - "ttp-native", "uuid", "x448", "zip", @@ -2367,16 +2428,6 @@ 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.106" @@ -2388,9 +2439,9 @@ dependencies = [ [[package]] name = "quinn" -version = "0.11.9" +version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", @@ -2408,9 +2459,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" dependencies = [ "aws-lc-rs", "bytes", @@ -2444,9 +2495,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] @@ -2491,7 +2542,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ "chacha20", - "getrandom 0.4.2", + "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -2564,7 +2615,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", ] [[package]] @@ -2573,14 +2624,14 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", ] [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2607,9 +2658,9 @@ checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -2621,8 +2672,8 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.14", - "http 1.4.1", + "h2 0.4.15", + "http 1.4.2", "http-body", "http-body-util", "hyper", @@ -2727,7 +2778,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "errno", "libc", "linux-raw-sys 0.12.1", @@ -2736,9 +2787,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.40" +version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" +checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ "aws-lc-rs", "log", @@ -2862,7 +2913,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2940,6 +2991,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -3040,9 +3104,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "serde", ] @@ -3189,7 +3253,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.12.1", + "bitflags 2.13.0", "byteorder", "bytes", "crc", @@ -3231,7 +3295,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.12.1", + "bitflags 2.13.0", "byteorder", "crc", "dotenvy", @@ -3307,12 +3371,6 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" - [[package]] name = "strum_macros" version = "0.27.2" @@ -3325,18 +3383,6 @@ dependencies = [ "syn", ] -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3345,9 +3391,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.117" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -3380,7 +3426,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3417,12 +3463,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.47" +version = "0.3.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -3432,15 +3477,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" dependencies = [ "num-conv", "time-core", @@ -3543,10 +3588,10 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.12.1", + "bitflags 2.13.0", "bytes", "futures-util", - "http 1.4.1", + "http 1.4.2", "http-body", "pin-project-lite", "tower", @@ -3605,33 +3650,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "ttp-core" -version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" -dependencies = [ - "base64", - "byteorder", - "rand 0.8.6", - "serde_json", - "strum 0.28.0", - "strum_macros 0.28.0", -] - -[[package]] -name = "ttp-native" -version = "0.1.0" -source = "git+https://git.methanium.net/Tensamin/TTP.git#23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2" -dependencies = [ - "quinn", - "rustls", - "rustls-native-certs", - "thiserror", - "tokio", - "ttp-core", - "wtransport", -] - [[package]] name = "typenum" version = "1.20.1" @@ -3687,6 +3705,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" @@ -3719,11 +3743,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" dependencies = [ - "getrandom 0.4.2", + "getrandom 0.4.3", "js-sys", "wasm-bindgen", ] @@ -3779,20 +3803,11 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.3+wasi-0.2.9" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", + "wit-bindgen", ] [[package]] @@ -3803,9 +3818,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" dependencies = [ "cfg-if", "once_cell", @@ -3816,9 +3831,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.72" +version = "0.4.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" dependencies = [ "js-sys", "wasm-bindgen", @@ -3826,9 +3841,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3836,9 +3851,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" dependencies = [ "bumpalo", "proc-macro2", @@ -3849,52 +3864,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags 2.12.1", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - [[package]] name = "web-sys" -version = "0.3.99" +version = "0.3.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621441cfc37b84979402712047321980c178f299193a3589d05b99e8763436" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" dependencies = [ "js-sys", "wasm-bindgen", @@ -3912,9 +3893,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" dependencies = [ "rustls-pki-types", ] @@ -4217,100 +4198,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - [[package]] name = "wit-bindgen" version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.12.1", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - [[package]] name = "writeable" version = "0.6.3" @@ -4394,9 +4287,9 @@ dependencies = [ [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4417,18 +4310,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.50" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -4458,18 +4351,18 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.8.2" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", @@ -4538,9 +4431,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" +checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index 22ec471..5d4e97e 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,8 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -ttp-core = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-core" } -ttp-native = { git = "https://git.methanium.net/Tensamin/TTP.git", package = "ttp-native" } +mtp = { git = "https://git.methanium.net/methanium/mtp", features = ["host"] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" diff --git a/src/notifications/tauri.rs b/src/notifications/tauri.rs index c57c26d..8195988 100644 --- a/src/notifications/tauri.rs +++ b/src/notifications/tauri.rs @@ -2,12 +2,12 @@ use crate::log; use crate::util::file_util::load_file_vec; use crate::util::logger::PrintType; use dashmap::DashMap; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; use once_cell::sync::Lazy; +use std::net::{IpAddr, Ipv4Addr}; use std::sync::Arc; use std::time::Duration; -use tokio::sync::futures; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; pub struct TauriConnection { pub user_id: i64, @@ -20,7 +20,8 @@ pub async fn start(port: u16) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = ttp_native::host( + let mut host: Host = host( + IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port, cert_pem, key_pem, @@ -59,45 +60,41 @@ async fn handle_connection(sender: Sender, receiver: &mut Receiver) { let sender = Arc::new(sender); while let Ok(cv) = receiver.receive().await { - match cv.get_type() { - CommunicationType::tauri_identification => { - let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); - if user_id != 0 { - current_user_id = user_id; - let conn = Arc::new(TauriConnection { - user_id, - sender: sender.clone(), - }); + if cv.is_type(CommunicationType::TauriIdentification) { + let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; + if user_id != 0 { + current_user_id = user_id; + let conn = Arc::new(TauriConnection { + user_id, + sender: sender.clone(), + }); - TAURI_CONNECTIONS - .entry(user_id) - .and_modify(|conns| { - conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); - conns.push(conn.clone()); - }) - .or_insert_with(|| vec![conn]); + TAURI_CONNECTIONS + .entry(user_id) + .and_modify(|conns| { + conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); + conns.push(conn.clone()); + }) + .or_insert_with(|| vec![conn]); - let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); - if let Err(e) = sender.send(&response).await { - log!( - PrintType::General, - "Failed to send tauri success response: {}", - e - ); - } else { - log!(user_id, PrintType::Client, "Tauri device registered"); - } - } - } - CommunicationType::ping => { let response = - CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); - if let Err(_) = sender.send(&response).await { - break; + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); + if let Err(e) = sender.send(&response).await { + log!( + PrintType::General, + "Failed to send tauri success response: {}", + e + ); + } else { + log!(user_id, PrintType::Client, "Tauri device registered"); } } - _ => {} + } else if cv.is_type(CommunicationType::Ping) { + let response = + CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); + if let Err(_) = sender.send(&response).await { + break; + } } } @@ -116,8 +113,8 @@ pub async fn send_notification(user_id: i64, sender_id: i64) { }; if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::push_notification) - .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + let cv = CommunicationValue::new(CommunicationType::PushNotification) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); let mut remove_needed = false; for conn in conns.iter() { @@ -141,8 +138,8 @@ pub async fn remove_notification(user_id: i64, sender_id: i64) { }; if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::read_notification) - .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + let cv = CommunicationValue::new(CommunicationType::ReadNotification) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); let mut remove_needed = false; for conn in conns.iter() { diff --git a/src/server/server.rs b/src/server/server.rs index 3c7fc05..c367679 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -28,7 +28,7 @@ pub async fn start(port: u16) -> anyhow::Result<()> { .with_no_client_auth() .with_single_cert(cert_chain, key)?; - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + config.alpn_protocols = vec![b"h2".to_vec(), b"hmtp/1.1".to_vec()]; let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); let addr = format!("{}:{}", bind_addr, port); diff --git a/src/server/short_link.rs b/src/server/short_link.rs index c0ab51d..b3f91bd 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -11,7 +11,7 @@ pub async fn add_short_link(long: &str) -> Result { LINKS.insert(raw.clone(), long.to_string()); Ok(format!( - "https://omega.tensamin.net/direct/{}", + "hmtps://omega.tensamin.net/direct/{}", format_with_dashes(&raw) )) } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 93804fd..cc53520 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -11,6 +11,9 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; +use std::net::{IpAddr, Ipv4Addr}; use rand::{Rng, distributions::Alphanumeric}; use std::{ sync::Arc, @@ -20,8 +23,6 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; -use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; -use ttp_native::{Host, Policy, Receiver, SendMode, Sender}; use x448::PublicKey; // ============================================================================ @@ -92,7 +93,7 @@ impl AuthState { } // ============================================================================ -// Omikron Connection (ttp/QUIC-based) +// Omikron Connection (mtp/QUIC-based) // ============================================================================ pub struct OmikronConnection { @@ -180,7 +181,7 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_in!(PrintType::Omikron, &cv); } @@ -193,7 +194,7 @@ impl OmikronConnection { } // Handle ping regardless of auth state - if cv.is_type(CommunicationType::ping) { + if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } @@ -213,22 +214,22 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::identification) { + if !cv.is_type(CommunicationType::Identification) { let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) .await; return Err(OmikronError::NotAuthenticated); } // Extract omikron ID let omikron_id = cv - .get_data(DataTypes::omikron_id) + .get_data(DataType::OmikronId) .as_number() .ok_or(OmikronError::InvalidResponse)?; log!("Omikron {:?} connected", omikron_id); // Lookup omikron in database - let (public_key, _) = get_omikron_by_id(omikron_id) + let (public_key, _) = get_omikron_by_id(omikron_id as i64) .await .map_err(|e| OmikronError::Sql(e.to_string()))?; @@ -261,7 +262,9 @@ impl OmikronConnection { log!("Stored Pubkey"); - *self.state.write().await = AuthState::Identified { omikron_id }; + *self.state.write().await = AuthState::Identified { + omikron_id: omikron_id as i64, + }; log!("Stored State"); @@ -279,28 +282,28 @@ impl OmikronConnection { log!("Encrypted Challenge"); // Send challenge response - let response = CommunicationValue::new(CommunicationType::challenge) + let response = CommunicationValue::new(CommunicationType::Challenge) .with_id(cv.get_id()) - .add_data( - DataTypes::public_key, + .add_typed_default( + DataType::PublicKey, DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), ) - .add_data(DataTypes::challenge, DataValue::Str(encrypted)); + .add_typed_default(DataType::Content, DataValue::Str(encrypted)); log!("Sending Challenge"); self.send(&response).await } async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::challenge_response) { + if !cv.is_type(CommunicationType::ChallengeResponse) { let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_not_authenticated) + .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) .await; return Err(OmikronError::NotAuthenticated); } let client_response = cv - .get_data(DataTypes::challenge) + .get_data(DataType::Content) .as_str() .ok_or(OmikronError::InvalidResponse)?; @@ -312,16 +315,16 @@ impl OmikronConnection { omikron_manager::add_omikron(self.clone()).await; - let response = CommunicationValue::new(CommunicationType::identification_response) + let response = CommunicationValue::new(CommunicationType::IdentificationResponse) .with_id(cv.get_id()) - .add_data(DataTypes::accepted, DataValue::Bool(true)); + .add_typed_default(DataType::Accepted, DataValue::Bool(true)); self.clone().send(&response).await?; log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); Ok(()) } else { let _ = self - .send_error_response(cv.get_id(), CommunicationType::error_invalid_challenge) + .send_error_response(cv.get_id(), CommunicationType::ErrorInvalidChallenge) .await; Err(OmikronError::AuthenticationFailed) } @@ -336,52 +339,53 @@ impl OmikronConnection { cv: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { - match cv.get_type() { + let comm_type = cv.get_comm_type_enum(); + match comm_type { // Link shortening - CommunicationType::shorten_link => self.handle_shorten_link(cv).await, + Some(CommunicationType::ShortenLink) => self.handle_shorten_link(cv).await, // Online status tracking - CommunicationType::user_connected => { + Some(CommunicationType::UserConnected) => { self.handle_user_connected(cv, omikron_id).await; Ok(()) } - CommunicationType::user_disconnected => { + Some(CommunicationType::UserDisconnected) => { self.handle_user_disconnected(cv, omikron_id).await; Ok(()) } - CommunicationType::iota_connected => { + Some(CommunicationType::IotaConnected) => { self.handle_iota_connected(cv, omikron_id).await; Ok(()) } - CommunicationType::iota_disconnected => { + Some(CommunicationType::IotaDisconnected) => { self.handle_iota_disconnected(cv, omikron_id).await; Ok(()) } - CommunicationType::sync_client_iota_status => { + Some(CommunicationType::SyncClientIotaStatus) => { self.handle_sync_status(cv, omikron_id).await; Ok(()) } - CommunicationType::get_user_data => self.handle_get_user_data(cv).await, - CommunicationType::get_iota_data => self.handle_get_iota_data(cv).await, + Some(CommunicationType::GetUserData) => self.handle_get_user_data(cv).await, + Some(CommunicationType::GetIotaData) => self.handle_get_iota_data(cv).await, - CommunicationType::get_register => self.handle_get_register(cv).await, - CommunicationType::complete_register_iota => { + Some(CommunicationType::GetRegister) => self.handle_get_register(cv).await, + Some(CommunicationType::CompleteRegisterIota) => { self.handle_complete_register_iota(cv).await } - CommunicationType::complete_register_user => { + Some(CommunicationType::CompleteRegisterUser) => { self.handle_complete_register_user(cv).await } - CommunicationType::change_user_data => self.handle_change_user_data(cv).await, - CommunicationType::change_iota_data => self.handle_change_iota_data(cv).await, - CommunicationType::delete_user => self.handle_delete_user(cv).await, - CommunicationType::delete_iota => self.handle_delete_iota(cv).await, + Some(CommunicationType::ChangeUserData) => self.handle_change_user_data(cv).await, + Some(CommunicationType::ChangeIotaData) => self.handle_change_iota_data(cv).await, + Some(CommunicationType::DeleteUser) => self.handle_delete_user(cv).await, + Some(CommunicationType::DeleteIota) => self.handle_delete_iota(cv).await, - CommunicationType::get_notifications => self.handle_get_notifications(cv).await, - CommunicationType::read_notification => self.handle_read_notification(cv).await, - CommunicationType::push_notification => self.handle_push_notification(cv).await, - CommunicationType::get_states => self.handle_get_states(cv).await, + Some(CommunicationType::GetNotifications) => self.handle_get_notifications(cv).await, + Some(CommunicationType::ReadNotification) => self.handle_read_notification(cv).await, + Some(CommunicationType::PushNotification) => self.handle_push_notification(cv).await, + Some(CommunicationType::GetStates) => self.handle_get_states(cv).await, _ => { log_err!( @@ -401,7 +405,7 @@ impl OmikronConnection { async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let link = cv - .get_data(DataTypes::link) + .get_data(DataType::Link) .as_str() .ok_or(OmikronError::InvalidResponse)?; @@ -409,32 +413,28 @@ impl OmikronConnection { .await .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; - let response = CommunicationValue::new(CommunicationType::shorten_link) + let response = CommunicationValue::new(CommunicationType::ShortenLink) .with_id(cv.get_id()) - .add_data(DataTypes::link, DataValue::Str(short)); + .add_typed_default(DataType::Link, DataValue::Str(short)); self.send(&response).await } async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { let status = cv - .get_data(DataTypes::user_state) + .get_data(DataType::UserState) .as_str() .and_then(|s| UserStatus::from_str(s)) .unwrap_or(UserStatus::user_online); - user_online_tracker::track_user_status( - user_id.try_into().unwrap(), - status, - omikron_id, - ); + user_online_tracker::track_user_status(user_id.try_into().unwrap(), status, omikron_id); } } async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { user_online_tracker::track_user_status( user_id as i64, @@ -447,7 +447,7 @@ impl OmikronConnection { async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA connected"); - let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = match cv.get_data(DataType::IotaId).as_number() { Some(id) => id as i64, None => return, }; @@ -456,7 +456,7 @@ impl OmikronConnection { let mut user_ids = Vec::new(); if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::Number(user_id.try_into().unwrap())); + user_ids.push(DataValue::SignedNumber(user_id.try_into().unwrap())); user_online_tracker::track_user_status( user_id.try_into().unwrap(), UserStatus::user_offline, @@ -467,16 +467,16 @@ impl OmikronConnection { log_in!(PrintType::General, "SQL error loading users for IOTA"); } - let response = CommunicationValue::new(CommunicationType::iota_user_data) + let response = CommunicationValue::new(CommunicationType::IotaUserData) .with_id(cv.get_id()) - .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); let _ = self.send(&response).await; } async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { log_in!(PrintType::Omega, "IOTA disconnected"); - let iota_id = match cv.get_data(DataTypes::iota_id).as_number() { + let iota_id = match cv.get_data(DataType::IotaId).as_number() { Some(id) => id as i64, None => return, }; @@ -490,11 +490,11 @@ impl OmikronConnection { } async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { - if let DataValue::Array(user_ids) = cv.get_data(DataTypes::user_ids) { + if let DataValue::Array(user_ids) = cv.get_data(DataType::UserIds) { for user_id_val in user_ids { - if let DataValue::Number(user_id) = user_id_val { + if let DataValue::SignedNumber(user_id) = user_id_val { user_online_tracker::track_user_status( - *user_id, + *user_id as i64, UserStatus::user_offline, omikron_id, ); @@ -502,10 +502,10 @@ impl OmikronConnection { } } - if let DataValue::Array(iota_ids) = cv.get_data(DataTypes::iota_ids) { + if let DataValue::Array(iota_ids) = cv.get_data(DataType::IotaIds) { for iota_id_val in iota_ids { - if let DataValue::Number(iota_id) = iota_id_val { - user_online_tracker::track_iota_connection(*iota_id, omikron_id, true); + if let DataValue::SignedNumber(iota_id) = iota_id_val { + user_online_tracker::track_iota_connection(*iota_id as i64, omikron_id, true); } } } @@ -513,7 +513,7 @@ impl OmikronConnection { async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by user_id first - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok(user_data) = get_by_user_id(user_id as i64).await { let response = self .clone() @@ -524,7 +524,7 @@ impl OmikronConnection { } // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok(user_data) = get_by_username(username).await { let response = self .clone() @@ -536,7 +536,7 @@ impl OmikronConnection { // Not found let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); self.send(&response).await } @@ -573,28 +573,28 @@ impl OmikronConnection { _, ) = user; - let mut response = CommunicationValue::new(CommunicationType::get_user_data) + let mut response = CommunicationValue::new(CommunicationType::GetUserData) .with_id(msg_id) - .add_data(DataTypes::username, DataValue::Str(username.clone())) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::user_id, DataValue::Number(id)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)) - .add_data(DataTypes::sub_level, DataValue::Number(sub_level as i64)) - .add_data(DataTypes::sub_end, DataValue::Number(sub_end)); + .add_typed_default(DataType::Username, DataValue::Str(username.clone())) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::SubLevel, DataValue::SignedNumber(sub_level as i128)) + .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); // Display name (fallback to username) let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); - response = response.add_data(DataTypes::display, DataValue::Str(display_name)); + response = response.add_typed_default(DataType::Display, DataValue::Str(display_name)); // Optional fields if let Some(s) = status.filter(|s| !s.is_empty()) { - response = response.add_data(DataTypes::status, DataValue::Str(s)); + response = response.add_typed_default(DataType::Status, DataValue::Str(s)); } if let Some(a) = about.filter(|a| !a.is_empty()) { - response = response.add_data(DataTypes::about, DataValue::Str(a)); + response = response.add_typed_default(DataType::About, DataValue::Str(a)); } if let Some(av) = avatar { - response = response.add_data(DataTypes::avatar, DataValue::Str(STANDARD.encode(av))); + response = response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); } // Online status @@ -608,24 +608,24 @@ impl OmikronConnection { } else { us.connection_type.clone() }; - response = response.add_data( - DataTypes::online_status, + response = response.add_typed_default( + DataType::OnlineStatus, DataValue::Str(display_status.to_string()), ); - response = response.add_data(DataTypes::omikron_id, DataValue::Number(us.omikron_id)); + response = response.add_typed_default(DataType::OmikronId, DataValue::SignedNumber(us.omikron_id.into())); } else { - response = response.add_data( - DataTypes::online_status, + response = response.add_typed_default( + DataType::OnlineStatus, DataValue::Str(UserStatus::iota_offline.to_string()), ); } - response = response.add_data( - DataTypes::omikron_connections, + response = response.add_typed_default( + DataType::OmikronConnections, DataValue::Array( iota_connections .into_iter() - .map(DataValue::Number) + .map(|id| DataValue::SignedNumber(id as i128)) .collect(), ), ); @@ -635,7 +635,7 @@ impl OmikronConnection { async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { // Try by iota_id - if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { let response = self .clone() @@ -646,7 +646,7 @@ impl OmikronConnection { } // Try by user_id - if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(user_id as i64).await { @@ -667,7 +667,7 @@ impl OmikronConnection { } // Try by username - if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_username(username).await { @@ -688,7 +688,7 @@ impl OmikronConnection { } let response = - CommunicationValue::new(CommunicationType::error_not_found).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); self.send(&response).await } @@ -700,27 +700,27 @@ impl OmikronConnection { user_id: Option, username: Option, ) -> CommunicationValue { - let mut response = CommunicationValue::new(CommunicationType::get_iota_data) + let mut response = CommunicationValue::new(CommunicationType::GetIotaData) .with_id(msg_id) - .add_data(DataTypes::public_key, DataValue::Str(public_key)) - .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); if let Some(uid) = user_id { - response = response.add_data(DataTypes::user_id, DataValue::Number(uid)); + response = response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); } if let Some(uname) = username { - response = response.add_data(DataTypes::username, DataValue::Str(uname)); + response = response.add_typed_default(DataType::Username, DataValue::Str(uname)); } let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - response.add_data( - DataTypes::omikron_connections, + response.add_typed_default( + DataType::OmikronConnections, DataValue::Array( iota_connections .into_iter() - .map(DataValue::Number) + .map(|id| DataValue::SignedNumber(id as i128)) .collect(), ), ) @@ -728,9 +728,9 @@ impl OmikronConnection { async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::get_register) + let response = CommunicationValue::new(CommunicationType::GetRegister) .with_id(cv.get_id()) - .add_data(DataTypes::user_id, DataValue::Number(register_id as i64)); + .add_typed_default(DataType::UserId, DataValue::SignedNumber(register_id as i128)); self.send(&response).await } @@ -738,24 +738,21 @@ impl OmikronConnection { self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let iota_id_opt = cv - .get_data(DataTypes::iota_id) - .as_number() - .map(|n| n as i64); + let iota_id_opt = cv.get_data(DataType::IotaId).as_number().map(|n| n as i64); - if let Some(public_key) = cv.get_data(DataTypes::public_key).as_str() { + if let Some(public_key) = cv.get_data(DataType::PublicKey).as_str() { if let Some(iota_id) = iota_id_opt { // Register existing IOTA match sql::register_complete_iota(iota_id, public_key.to_string()).await { Ok(_) => { - let response = CommunicationValue::new(CommunicationType::success) + let response = CommunicationValue::new(CommunicationType::Success) .with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } @@ -764,21 +761,21 @@ impl OmikronConnection { match sql::create_new_iota(public_key.to_string()).await { Ok(new_iota_id) => { let response = - CommunicationValue::new(CommunicationType::complete_register_iota) + CommunicationValue::new(CommunicationType::CompleteRegisterIota) .with_id(cv.get_id()) - .add_data(DataTypes::iota_id, DataValue::Number(new_iota_id)); + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(new_iota_id.into())); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) .await } } @@ -787,21 +784,18 @@ impl OmikronConnection { self: Arc, cv: CommunicationValue, ) -> OmikronResult<()> { - let user_id = cv - .get_data(DataTypes::user_id) - .as_number() - .map(|n| n as i64); + let user_id = cv.get_data(DataType::UserId).as_number().map(|n| n as i64); let username = cv - .get_data(DataTypes::username) + .get_data(DataType::Username) .as_str() .map(|s| s.to_string()); let public_key = cv - .get_data(DataTypes::public_key) + .get_data(DataType::PublicKey) .as_str() .map(|s| s.to_string()); let iota_id = cv.get_sender(); let reset_token = cv - .get_data(DataTypes::reset_token) + .get_data(DataType::ResetToken) .as_str() .map(|s| s.to_string()); @@ -811,18 +805,18 @@ impl OmikronConnection { match sql::register_complete_user(uid, uname, pk, iota_id as i64, rt).await { Ok(_) => { let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) .await } } @@ -833,39 +827,39 @@ impl OmikronConnection { let mut error_message = String::new(); // Process each field - if let Some(username) = cv.get_data(DataTypes::username).as_str() { + if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Err(e) = sql::change_username(user_id, username.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(display) = cv.get_data(DataTypes::display).as_str() { + if let Some(display) = cv.get_data(DataType::Display).as_str() { if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(avatar) = cv.get_data(DataTypes::avatar).as_str() { + if let Some(avatar) = cv.get_data(DataType::Avatar).as_str() { if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(about) = cv.get_data(DataTypes::about).as_str() { + if let Some(about) = cv.get_data(DataType::About).as_str() { if let Err(e) = sql::change_about(user_id, about.to_string()).await { success = false; error_message = e.to_string(); } } - if let Some(status) = cv.get_data(DataTypes::status).as_str() { + if let Some(status) = cv.get_data(DataType::Status).as_str() { if let Err(e) = sql::change_status(user_id, status.to_string()).await { success = false; error_message = e.to_string(); } } if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataTypes::public_key).as_str(), - cv.get_data(DataTypes::private_key_hash).as_str(), + cv.get_data(DataType::PublicKey).as_str(), + cv.get_data(DataType::PrivateKeyHash).as_str(), ) { if let Err(e) = sql::change_keys( user_id, @@ -880,12 +874,12 @@ impl OmikronConnection { } if success { - let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } else { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); + .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); self.send(&response).await } } @@ -895,8 +889,8 @@ impl OmikronConnection { if let (iota_id, Some(reset_token), Some(new_token)) = ( cv.get_sender(), - cv.get_data(DataTypes::reset_token).as_str(), - cv.get_data(DataTypes::new_token).as_str(), + cv.get_data(DataType::ResetToken).as_str(), + cv.get_data(DataType::NewToken).as_str(), ) { match sql::get_by_user_id(user_id).await { Ok(user) => { @@ -918,31 +912,31 @@ impl OmikronConnection { } if success { - let response = CommunicationValue::new(CommunicationType::success) + let response = CommunicationValue::new(CommunicationType::Success) .with_id(cv.get_id()); self.send(&response).await } else { let response = - CommunicationValue::new(CommunicationType::error_internal) + CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(error_message)); + .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); self.send(&response).await } } else { self.send_error_response( cv.get_id(), - CommunicationType::error_invalid_challenge, + CommunicationType::ErrorInvalidChallenge, ) .await } } Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::error_not_found) + self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound) .await } } } else { - self.send_error_response(cv.get_id(), CommunicationType::error_invalid_data) + self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) .await } } @@ -952,13 +946,13 @@ impl OmikronConnection { match sql::delete_user(user_id).await { Ok(_) => { let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } @@ -969,13 +963,13 @@ impl OmikronConnection { match sql::delete_iota(iota_id as i64).await { Ok(_) => { let response = - CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); self.send(&response).await } Err(e) => { - let response = CommunicationValue::new(CommunicationType::error_internal) + let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_data(DataTypes::error_type, DataValue::Str(e.to_string())); + .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); self.send(&response).await } } @@ -990,9 +984,10 @@ impl OmikronConnection { Ok(notifications) => notifications .into_iter() .map(|(sender, amount)| { + let tm = mtp::type_map::TypeMap::latest(); DataValue::Container(vec![ - (DataTypes::sender_id, DataValue::Number(sender)), - (DataTypes::amount, DataValue::Number(amount)), + (DataType::SenderId.to_id(&tm), DataValue::SignedNumber(sender.into())), + (DataType::Amount.to_id(&tm), DataValue::SignedNumber(amount.into())), ]) }) .collect(), @@ -1002,9 +997,9 @@ impl OmikronConnection { } }; - let response = CommunicationValue::new(CommunicationType::get_notifications) + let response = CommunicationValue::new(CommunicationType::GetNotifications) .with_id(cv.get_id()) - .add_data(DataTypes::notifications, DataValue::Array(response_array)); + .add_typed_default(DataType::Notifications, DataValue::Array(response_array)); self.send(&response).await } @@ -1014,21 +1009,21 @@ impl OmikronConnection { ) -> OmikronResult<()> { let receiver_id = match cv.get_sender() { s if s > 0 => s as i64, - _ => match cv.get_data(DataTypes::receiver_id).as_number() { + _ => match cv.get_data(DataType::ReceiverId).as_number() { Some(id) => id as i64, None => return Ok(()), }, }; if let Some(other_id) = cv - .get_data(DataTypes::sender_id) + .get_data(DataType::SenderId) .as_number() .map(|n| n as i64) { if let Err(e) = sql::read_notification(receiver_id, other_id).await { log!(PrintType::General, "SQL read_notification error: {}", e); } else { - let response = CommunicationValue::new(CommunicationType::read_notification) + let response = CommunicationValue::new(CommunicationType::ReadNotification) .with_id(cv.get_id()); let _ = self.send(&response).await; @@ -1036,9 +1031,9 @@ impl OmikronConnection { crate::notifications::tauri::remove_notification(receiver_id, other_id).await; // Sync with other Omikron clients - let sync_cv = CommunicationValue::new(CommunicationType::read_notification) + let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) - .add_data(DataTypes::sender_id, DataValue::Number(other_id)); + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(other_id.into())); crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; } } @@ -1051,13 +1046,13 @@ impl OmikronConnection { ) -> OmikronResult<()> { let receiver_id = match cv.get_receiver() { r if r > 0 => r as i64, - _ => match cv.get_data(DataTypes::receiver_id).as_number() { + _ => match cv.get_data(DataType::ReceiverId).as_number() { Some(id) => id as i64, None => return Ok(()), }, }; - let sender_id = match cv.get_data(DataTypes::sender_id).as_number() { + let sender_id = match cv.get_data(DataType::SenderId).as_number() { Some(id) => id as i64, None => cv.get_sender() as i64, }; @@ -1066,31 +1061,31 @@ impl OmikronConnection { log!(PrintType::General, "SQL add_notification error: {}", e); } else { let response = - CommunicationValue::new(CommunicationType::push_notification).with_id(cv.get_id()); + CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); let _ = self.send(&response).await; // Sync with Tauri crate::notifications::tauri::send_notification(receiver_id, sender_id).await; // Sync with other Omikron clients - let push_cv = CommunicationValue::new(CommunicationType::push_notification) + let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) - .add_data(DataTypes::sender_id, DataValue::Number(sender_id)); + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id.into())); crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; } Ok(()) } async fn handle_get_states(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_ids = match cv.get_data(DataTypes::user_ids) { + let user_ids = match cv.get_data(DataType::UserIds) { DataValue::Array(ids) => ids, _ => return Ok(()), }; let mut states = Vec::new(); for id_val in user_ids { - if let DataValue::Number(user_id) = id_val { - let user_id = *user_id; + if let DataValue::SignedNumber(user_id) = id_val { + let user_id = *user_id as i64; let status = user_online_tracker::get_user_status(user_id); let status_str = match status { Some(ref us) => { @@ -1102,25 +1097,26 @@ impl OmikronConnection { } None => UserStatus::iota_offline.to_string(), }; + let tm = mtp::type_map::TypeMap::latest(); let mut map = Vec::new(); - map.push((DataTypes::user_id, DataValue::Number(user_id))); - map.push((DataTypes::user_state, DataValue::Str(status_str))); + map.push((DataType::UserId.to_id(&tm), DataValue::SignedNumber(user_id.into()))); + map.push((DataType::UserState.to_id(&tm), DataValue::Str(status_str))); states.push(DataValue::Container(map)); } } - let response = CommunicationValue::new(CommunicationType::get_states) + let response = CommunicationValue::new(CommunicationType::GetStates) .with_id(cv.get_id()) - .add_data(DataTypes::user_states, DataValue::Array(states)); + .add_typed_default(DataType::UserStates, DataValue::Array(states)); self.send(&response).await } async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { - *self.ping.write().await = *last_ping; + if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { + *self.ping.write().await = *last_ping as i64; } - let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + let response = CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); self.send(&response).await } @@ -1129,7 +1125,7 @@ impl OmikronConnection { // ------------------------------------------------------------------------- async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_out!(PrintType::Omikron, cv); } @@ -1196,7 +1192,8 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = ttp_native::host( + let mut host: Host = host( + IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, cert_pem, key_pem, diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 70c73cd..f98a75c 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,9 +1,9 @@ use crate::transport::omikron_connection::OmikronConnection; use dashmap::DashMap; +use mtp::codec::CommunicationValue; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; use std::sync::Arc; -use ttp_core::CommunicationValue; pub static OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(|| DashMap::new()); diff --git a/src/util/logger.rs b/src/util/logger.rs index 845489f..31606b6 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -1,5 +1,4 @@ use std::{ - collections::BTreeMap, fs::{self, OpenOptions}, io::Write, path::Path, @@ -9,7 +8,7 @@ use std::{ }; use ansi_term::Color; -use ttp_core::{CommunicationValue, DataTypes, DataValue}; +use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version}; use crate::util::file_util::get_directory; @@ -248,17 +247,19 @@ pub fn format_cv(cv: &CommunicationValue) -> String { let comm_type = cv.get_type().to_string(); parts.push(format!("{}", comm_type)); - let data: &BTreeMap = cv.get_data_container(); + let data = cv.data(); - let formated_data = - format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); + let formated_data = format_data_container( + data.iter().map(|(k, v)| (*k, v.clone())).collect(), + Version(1, 0), + ); parts.push(format!("{}", formated_data)); parts.join(": ") } -fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { +fn format_data_container(data: Vec<(DataTypeId, DataValue)>, version: Version) -> String { let parts: Vec = data .into_iter() .map(|(key, value)| { @@ -268,12 +269,13 @@ fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); + let inner_formatted = + format_data_container(inner, version.clone()); format!("{}={{ {} }}", key_str, inner_formatted) } DataValue::Array(arr) => { - let arr_formatted = format_array(arr); + let arr_formatted = format_array(arr, version.clone()); format!("{}=[{}]", key_str, arr_formatted) } @@ -282,7 +284,7 @@ fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { DataValue::BoolTrue => format!("{}=true", key_str), DataValue::BoolFalse => format!("{}=false", key_str), - DataValue::Number(num) => format!("{}={}", key_str, num), + DataValue::SignedNumber(num) => format!("{}={}", key_str, num), _ => "".to_string(), } @@ -292,19 +294,19 @@ fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { parts.join(", ") } -fn format_array(arr: Vec) -> String { +fn format_array(arr: Vec, version: Version) -> String { let parts: Vec = arr .into_iter() .map(|value| match value { DataValue::Str(s) => format!("\"{}\"", s), DataValue::Container(inner) => { - let inner_formatted = format_data_container(inner); + let inner_formatted = format_data_container(inner, version.clone()); format!("{{ {} }}", inner_formatted) } DataValue::Array(inner_arr) => { - let formatted = format_array(inner_arr); + let formatted = format_array(inner_arr, version.clone()); format!("[{}]", formatted) } @@ -313,7 +315,7 @@ fn format_array(arr: Vec) -> String { DataValue::BoolTrue => "true".to_string(), DataValue::BoolFalse => "false".to_string(), - DataValue::Number(num) => num.to_string(), + DataValue::SignedNumber(num) => num.to_string(), _ => String::new(), }) diff --git a/type-maps.yaml b/type-maps.yaml new file mode 100644 index 0000000..73a15e3 --- /dev/null +++ b/type-maps.yaml @@ -0,0 +1,240 @@ +# The version a Client should use +protocol_version: "1.0" + +# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended +# Fixed CommunicationType markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorBadVersion: 2 +# Disconnect: 3 +# Redirect: 4 +# Shutdown: 5 +# BadRequest: 6 +# Unauthorized: 7 +# Forbidden: 8 +# NotFound: 9 +# TooManyRequests: 10 +# InternalServerError: 11 +# BadGateway: 12 +# ServiceUnavailable: 13 +# GatewayTimeout: 14 +# Identification: 15 +# IdentificationResponse: 16 +# Register: 17 +# RegisterResponse: 18 +# Ping: 19 +# Pong: 20 +# +# Fixed Data Type markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorMessage: 2 +# Version: 3 +# Description: 4 +# Timestamp: 5 +# Id: 6 +# ClientNonce: 7 +# ServerNonce: 8 +# PublicKeys: 9 +# Signature: 10 +# Connected: 11 +# +# If a Type can't be used it will be mapped to 0 + +type_maps: + "1.0": # Protocol version 1.0 + CommunicationTypes: + ErrorProtocol: 33 + ErrorAnonymous: 34 + ErrorInternal: 35 + ErrorInvalidData: 36 + ErrorInvalidUserId: 37 + ErrorInvalidOmikronId: 38 + ErrorNotFound: 39 + ErrorNotAuthenticated: 40 + ErrorNoIota: 41 + ErrorInvalidChallenge: 42 + ErrorInvalidSecret: 43 + ErrorInvalidPrivateKey: 44 + ErrorInvalidPublicKey: 45 + ErrorNoUserId: 46 + ErrorNoCallId: 47 + ErrorInvalidCallId: 48 + Success: 49 + ShortenLink: 50 + SettingsSave: 51 + SettingsLoad: 52 + SettingsList: 53 + GlobalSettingsSave: 54 + GlobalSettingsLoad: 55 + Message: 56 + MessageState: 57 + MessageSend: 58 + MessageLive: 59 + MessageOtherIota: 60 + MessageChunk: 61 + MessagesGet: 62 + PushNotification: 63 + ReadNotification: 64 + GetNotifications: 65 + TauriIdentification: 66 + ChangeConfirm: 67 + ConfirmReceive: 68 + ConfirmRead: 69 + GetChats: 70 + GetStates: 71 + AddCommunity: 72 + RemoveCommunity: 73 + GetCommunities: 74 + RegisterIota: 81 + RegisterIotaSuccess: 82 + AddConversation: 85 + SendChat: 86 + ClientChanged: 87 + ClientConnected: 88 + ClientDisconnected: 89 + ClientClosed: 90 + PublicKey: 91 + PrivateKey: 92 + WebrtcSdp: 93 + WebrtcIce: 94 + StartStream: 95 + EndStream: 96 + WatchStream: 97 + CallToken: 98 + CallInvite: 99 + CallDisconnectUser: 100 + CallTimeoutUser: 101 + CallSetAnonymousJoining: 102 + CallData: 103 + EndCall: 104 + Function: 105 + Update: 106 + CreateUser: 107 + RhoUpdate: 108 + UserConnected: 109 + UserDisconnected: 110 + IotaConnected: 111 + IotaDisconnected: 112 + SyncClientIotaStatus: 113 + GetUserData: 114 + GetIotaData: 115 + IotaUserData: 116 + ChangeUserData: 117 + ChangeIotaData: 118 + GetRegister: 119 + CompleteRegisterUser: 120 + CompleteRegisterIota: 121 + DeleteUser: 122 + DeleteIota: 123 + StartRegister: 124 + CompleteRegister: 125 + GetApp: 126 + CreateApp: 127 + DeleteApp: 128 + SaveAppData: 129 + LoadAppData: 130 + AppIdentification: 131 + AppChallenge: 132 + AppChallengeResponse: 133 + AppIdentificationResponse: 134 + LoadTxtRecord: 135 + DataTypes: + ErrorType: 32 + ErrorProtocol: 33 + AcceptedIds: 34 + Uuid: 35 + RegisterId: 36 + Link: 37 + Settings: 38 + SettingsName: 39 + ChatPartnerId: 40 + ChatPartnerName: 41 + IotaId: 42 + UserId: 43 + UserIds: 44 + IotaIds: 45 + UserState: 46 + UserStates: 47 + UserPings: 48 + CallState: 49 + ScreenShare: 50 + PrivateKeyHash: 51 + Accepted: 52 + AcceptedProfiles: 53 + DeniedProfiles: 54 + Content: 55 + Messages: 56 + Notifications: 57 + SendTime: 58 + GetTime: 59 + GetVariant: 60 + SharedSecretOwn: 61 + SharedSecretOther: 62 + SharedSecretSign: 63 + SharedSecret: 64 + CallId: 65 + CallToken: 66 + CallSecret: 67 + Untill: 68 + Enabled: 69 + StartDate: 70 + EndDate: 71 + ReceiverId: 72 + SenderId: 73 + Signed: 75 + Message: 76 + MessageState: 77 + LastPing: 78 + PingIota: 79 + PingClients: 80 + Matches: 81 + Omikron: 82 + Offset: 83 + Amount: 84 + Position: 85 + Name: 86 + Path: 87 + Codec: 88 + Function: 89 + Payload: 90 + Result: 91 + Interactables: 92 + WantToWatch: 93 + Watcher: 94 + CreatedAt: 95 + Username: 96 + Display: 97 + Avatar: 98 + About: 99 + Status: 100 + PublicKey: 101 + SubLevel: 102 + SubEnd: 103 + CommunityAddress: 104 + CommunityTitle: 106 + Communities: 107 + RhoConnections: 108 + User: 109 + OnlineStatus: 110 + OmikronId: 111 + OmikronConnections: 112 + ResetToken: 113 + NewToken: 114 + CallInvited: 115 + CallMembers: 116 + Calls: 117 + Timeout: 118 + HasAdmin: 119 + LastMessageAt: 120 + Height: 121 + SentBySelf: 122 + SessionId: 123 + Contacts: 124 + LastMessage: 125 + AppIdentifier: 127 + AppPrivateKey: 128 + AppPublicKey: 129 + AppSession: 130 + AppData: 131 + TauriToken: 132 From 275d1cb4c42d99ec011533750c84c436202b6782 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 06:28:09 +0200 Subject: [PATCH 159/220] Updated Crypto to use MTP-Crypto --- Cargo.lock | 358 +++++++++++++++++++++++++++++++++++--- Cargo.toml | 6 +- src/main.rs | 22 ++- src/server/api.rs | 7 +- src/util/crypto_helper.rs | 137 --------------- src/util/crypto_util.rs | 201 --------------------- src/util/mod.rs | 2 - 7 files changed, 360 insertions(+), 373 deletions(-) delete mode 100644 src/util/crypto_helper.rs delete mode 100644 src/util/crypto_util.rs diff --git a/Cargo.lock b/Cargo.lock index 1f85cb3..4e5fdac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,6 +674,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.1" @@ -685,6 +696,19 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "cipher" version = "0.4.4" @@ -693,6 +717,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", "inout", + "zeroize", ] [[package]] @@ -704,6 +729,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "combine" version = "4.6.7" @@ -861,7 +892,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -873,6 +906,42 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.2.1" @@ -910,6 +979,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid 0.10.2", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -985,6 +1064,7 @@ dependencies = [ "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -1016,13 +1096,37 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "ed448-goldilocks" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" dependencies = [ - "fiat-crypto", + "fiat-crypto 0.1.20", "hex", "subtle", ] @@ -1120,6 +1224,12 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1451,7 +1561,16 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac 0.13.0", ] [[package]] @@ -1463,6 +1582,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "home" version = "0.5.12" @@ -1540,6 +1668,7 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ + "ctutils", "typenum", ] @@ -1846,6 +1975,25 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2029,16 +2177,84 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-dsa" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" +dependencies = [ + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", + "hybrid-array", + "module-lattice", + "pkcs8 0.11.0", + "shake", + "signature 3.0.0", +] + +[[package]] +name = "mlkem-rs" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0965b8b081668ff0398dc5e9dc3f2ebb9e833393f4ab5b9f725ddce11acef8" +dependencies = [ + "rand_core 0.6.4", + "serde", + "sha3", + "subtle", + "zeroize", +] + +[[package]] +name = "mlkem-tls" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b205d031298adf904d88efd6a57862d8650a4ab754aade19a9b5e87040bf4e" +dependencies = [ + "mlkem-rs", + "rand_core 0.6.4", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "mtp" version = "0.1.0" source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "mtp-client", + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-host", + "mtp-transport", + "mtp-type-map", +] + +[[package]] +name = "mtp-client" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" dependencies = [ "mtp-codec", "mtp-common", - "mtp-host", + "mtp-crypto", "mtp-transport", - "mtp-type-map", + "rand 0.8.6", + "tokio", ] [[package]] @@ -2049,6 +2265,7 @@ dependencies = [ "base64", "byteorder", "mtp-common", + "mtp-crypto", "mtp-type-map", "rand 0.8.6", ] @@ -2064,6 +2281,23 @@ dependencies = [ "wtransport", ] +[[package]] +name = "mtp-crypto" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "chacha20poly1305", + "ed25519-dalek", + "getrandom 0.4.3", + "hkdf 0.13.0", + "ml-dsa", + "mlkem-tls", + "rand_core 0.6.4", + "serde", + "sha2 0.11.0", + "zeroize", +] + [[package]] name = "mtp-host" version = "0.1.0" @@ -2071,6 +2305,7 @@ source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a43 dependencies = [ "mtp-codec", "mtp-common", + "mtp-crypto", "mtp-transport", "rand 0.8.6", "tokio", @@ -2198,9 +2433,10 @@ dependencies = [ "dashmap", "dotenv", "hex", - "hkdf", + "hkdf 0.12.4", "json", "mtp", + "mtp-crypto", "once_cell", "rand 0.8.6", "rand_core 0.6.4", @@ -2272,7 +2508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -2329,9 +2565,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] @@ -2340,8 +2576,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", ] [[package]] @@ -2386,6 +2632,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -2541,7 +2798,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20", + "chacha20 0.10.1", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -2726,10 +2983,10 @@ dependencies = [ "num-integer", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", - "signature", - "spki", + "signature 2.2.0", + "spki 0.7.3", "subtle", "zeroize", ] @@ -3048,6 +3305,27 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" @@ -3074,6 +3352,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -3157,9 +3445,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", ] +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.0", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + [[package]] name = "sqlx" version = "0.8.6" @@ -3266,8 +3570,8 @@ dependencies = [ "futures-util", "generic-array", "hex", - "hkdf", - "hmac", + "hkdf 0.12.4", + "hmac 0.12.1", "itoa", "log", "md-5", @@ -3304,8 +3608,8 @@ dependencies = [ "futures-core", "futures-util", "hex", - "hkdf", - "hmac", + "hkdf 0.12.4", + "hmac 0.12.1", "home", "itoa", "log", @@ -4246,6 +4550,18 @@ dependencies = [ "url", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "x448" version = "0.6.0" @@ -4416,7 +4732,7 @@ dependencies = [ "deflate64", "flate2", "getrandom 0.3.4", - "hmac", + "hmac 0.12.1", "indexmap", "lzma-rust2", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 5d4e97e..f227b9e 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,10 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/methanium/mtp", features = ["host"] } +mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ + "host", + "crypto", +] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" @@ -36,3 +39,4 @@ uuid = { version = "1.19.0", features = ["v4"] } x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" +mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = ["pqc"] } diff --git a/src/main.rs b/src/main.rs index 40bf10a..9429e59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,24 +8,30 @@ use crate::notifications::tauri; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::transport::omikron_connection; -use crate::util::crypto_helper::load_public_key; -use crate::util::crypto_helper::load_secret_key; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; +use base64::Engine as _; use dotenv::from_path; +use mtp_crypto::{Keyring, PublicKeyBundle}; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; -static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); -pub fn get_private_key() -> x448::Secret { - load_secret_key(&*PRIVATE_KEY).unwrap() +static KEYRING_ENV: Lazy = Lazy::new(|| env::var("KEYRING").unwrap()); +fn load_keyring() -> Keyring { + let bytes = base64::engine::general_purpose::STANDARD + .decode(&*KEYRING_ENV) + .expect("Invalid KEYRING env var: not valid base64"); + Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize") } -static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); -pub fn get_public_key() -> x448::PublicKey { - load_public_key(&*PUBLIC_KEY).unwrap() +pub fn get_keyring() -> &'static Keyring { + static KEYRING: Lazy = Lazy::new(load_keyring); + &KEYRING +} +pub fn get_public_key_bundle() -> PublicKeyBundle { + get_keyring().public_key_bundle() } #[tokio::main] diff --git a/src/server/api.rs b/src/server/api.rs index eb6f9cf..c9a1c59 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,16 +1,16 @@ -use crate::get_public_key; +use crate::get_public_key_bundle; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, - util::crypto_helper::public_key_to_base64, }; use actix_web::HttpResponse; use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; +use mtp_crypto::PublicKeyBundle; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { if path == "OPTIONS" { @@ -195,7 +195,8 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { ["api", "get", "public_key"] => { let mut res = JsonValue::new_object(); res["status"] = "success".into(); - res["public_key"] = public_key_to_base64(&get_public_key()).into(); + let bundle = get_public_key_bundle(); + res["public_key"] = base64::engine::general_purpose::STANDARD.encode(bundle.to_bytes()).into(); (StatusCode::OK, res.dump()) } diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs deleted file mode 100644 index 68e3855..0000000 --- a/src/util/crypto_helper.rs +++ /dev/null @@ -1,137 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, OsRng}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use rand_core::RngCore; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret, SharedSecret}; - -/// Errors for crypto operations -#[derive(Debug)] -pub enum CryptoError { - Base64Decode, - AgreementError, - EncryptionError(aes_gcm::Error), - DecryptionError(aes_gcm::Error), -} - -impl From for CryptoError { - fn from(_: base64::DecodeError) -> Self { - CryptoError::Base64Decode - } -} - -pub fn generate_keypair() -> (Secret, PublicKey) { - let mut buf = [0u8; 56]; - let mut rng = OsRng; - rng.fill_bytes(&mut buf); - let secret = Secret::from_bytes(&buf).unwrap(); - let public = PublicKey::from(&secret); - (secret, public) -} - -pub fn public_key_to_base64(pubkey: &PublicKey) -> String { - STANDARD.encode(pubkey.as_bytes().as_ref()) -} - -pub fn secret_key_to_base64(secret: &Secret) -> String { - STANDARD.encode(secret.as_bytes().as_ref()) -} - -pub fn load_public_key(base64_pub: &str) -> Option { - let bytes = STANDARD.decode(base64_pub).unwrap(); - PublicKey::from_bytes(&bytes) -} - -pub fn load_secret_key(base64_secret: &str) -> Option { - let bytes = STANDARD.decode(base64_secret).unwrap(); - Secret::from_bytes(&bytes) -} - -fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(shared.as_bytes()); - let result = hasher.finalize(); - let mut key = [0u8; 32]; - key.copy_from_slice(&result[..32]); - key -} - -pub fn encrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - plaintext: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - encrypt(secret, peer_pub, plaintext) -} -pub fn encrypt( - secret: Secret, - peer_pub: PublicKey, - plaintext: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - let mut nonce_bytes = [0u8; 12]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - let ciphertext = cipher - .encrypt(nonce, plaintext.as_bytes()) - .map_err(CryptoError::EncryptionError)?; - // prefix nonce to ciphertext - let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); - out.extend_from_slice(&nonce_bytes); - out.extend_from_slice(&ciphertext); - Ok(STANDARD.encode(&out)) -} - -pub fn decrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - encrypted_base64: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - decrypt(secret, peer_pub, encrypted_base64) -} -pub fn decrypt( - secret: Secret, - peer_pub: PublicKey, - encrypted_base64: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - - let encrypted = STANDARD.decode(encrypted_base64)?; - if encrypted.len() < 12 { - return Err(CryptoError::DecryptionError(aes_gcm::Error)); - } - let nonce_bytes = &encrypted[..12]; - let ciphertext = &encrypted[12..]; - let nonce = Nonce::from_slice(nonce_bytes); - let plaintext_bytes = cipher - .decrypt(nonce, ciphertext) - .map_err(CryptoError::DecryptionError)?; - let plaintext = String::from_utf8(plaintext_bytes) - .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; - Ok(plaintext) -} - -pub fn hash_it(input: &str) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(input.as_bytes()); - hasher.finalize().to_vec() -} - -pub fn hex_hash(input: &str) -> String { - let digest = hash_it(input); - digest.iter().map(|b| format!("{:02x}", b)).collect() -} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs deleted file mode 100644 index 97acab4..0000000 --- a/src/util/crypto_util.rs +++ /dev/null @@ -1,201 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, Payload}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; -use hkdf::Hkdf; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret}; - -// --- Custom Errors --- -#[derive(Debug)] -pub enum SecurePayloadError { - InvalidBase64, - InvalidHex, - EncryptionError, - DecryptionError, - InvalidKeyLength, -} - -// --- Data Format Enum --- -#[derive(Clone, Copy, Debug)] -pub enum DataFormat { - Raw, - Base64, - Hex, -} - -// --- Main Class Structure --- -pub struct SecurePayload { - /// The internal canonical representation is always raw bytes. - inner_data: Vec, - /// The private key of the user associated with this payload instance. - private_key: Secret, -} - -impl Clone for SecurePayload { - fn clone(&self) -> Self { - Self { - inner_data: self.inner_data.clone(), - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - } - } -} - -impl SecurePayload { - /// Clear Constructor: Takes data in any format and the user's private key. - pub fn new>( - data: T, - format: DataFormat, - private_key: S, - ) -> Result - where - S: Into, - { - let raw_data = match format { - DataFormat::Raw => data.as_ref().to_vec(), - DataFormat::Base64 => BASE64_STD - .decode(data.as_ref()) - .map_err(|_| SecurePayloadError::InvalidBase64)?, - DataFormat::Hex => { - hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? - } - }; - - Ok(Self { - inner_data: raw_data, - private_key: private_key.into(), - }) - } - - /// Helper to get the public key associated with this instance's private key. - pub fn get_public_key(&self) -> [u8; 56] { - *PublicKey::from(&self.private_key).as_bytes() - } - - /// Exports the internal data to the requested format - pub fn export(&self, format: DataFormat) -> String { - match format.into() { - DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), - DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), - DataFormat::Hex => hex::encode(&self.inner_data), - } - } - - /// Access raw bytes directly - pub fn get_bytes(&self) -> &[u8] { - &self.inner_data - } - - /// Returns the SHA-256 Hash of the data in the requested format - pub fn get_hash(&self, format: DataFormat) -> String { - let mut hasher = Sha256::new(); - hasher.update(&self.inner_data); - let result = hasher.finalize(); - - match format { - DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), - DataFormat::Base64 => BASE64_STD.encode(result), - DataFormat::Hex => hex::encode(result), - } - } - - /// Encrypts the held data for a specific recipient using AES-256-GCM. - /// The message will contain ONLY the ciphertext. - pub fn encrypt_x448(&self, public_key: S) -> Result - where - S: Into, - { - let peer_pub = public_key.into(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - println!( - "Encryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 3. Key & Nonce Derivation (HKDF) - // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - // 4. Encrypt with AES-256-GCM - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let ciphertext = cipher - .encrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - // 5. Result is ONLY the ciphertext. No key or nonce is packed. - Ok(SecurePayload { - inner_data: ciphertext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } - - /// Decrypts the held data providing the sender's public key manually. - pub fn decrypt_to_format( - &self, - peer_public_key_bytes: &[u8; 56], - output_format: DataFormat, - ) -> Result { - let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; - Ok(decrypted_instance.export(output_format)) - } - - /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. - pub fn decrypt_x448( - &self, - peer_public_key_bytes: &[u8; 56], - ) -> Result { - // 1. Perform Exchange - let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - // LOGGING: Shared Secret - println!( - "Decryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 2. Key & Nonce Derivation (Must match encryption exactly) - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - // 3. Decrypt with AES-256-GCM - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let plaintext = cipher - .decrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - Ok(SecurePayload { - inner_data: plaintext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs index 1897223..d0cc1dd 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,2 @@ -pub mod crypto_helper; -pub mod crypto_util; pub mod file_util; pub mod logger; From f0011a67cf6c635965be18cf309f1493875a485a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 06:28:09 +0200 Subject: [PATCH 160/220] Updated Crypto to use MTP-Crypto --- Cargo.lock | 358 +++++++++++++++++++++++++++++++++++--- Cargo.toml | 6 +- src/main.rs | 22 ++- src/server/api.rs | 7 +- src/util/crypto_helper.rs | 137 --------------- src/util/crypto_util.rs | 201 --------------------- src/util/mod.rs | 2 - 7 files changed, 360 insertions(+), 373 deletions(-) delete mode 100644 src/util/crypto_helper.rs delete mode 100644 src/util/crypto_util.rs diff --git a/Cargo.lock b/Cargo.lock index 1f85cb3..4e5fdac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,6 +674,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.1" @@ -685,6 +696,19 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "cipher" version = "0.4.4" @@ -693,6 +717,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", "inout", + "zeroize", ] [[package]] @@ -704,6 +729,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "combine" version = "4.6.7" @@ -861,7 +892,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -873,6 +906,42 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.2.1" @@ -910,6 +979,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid 0.10.2", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -985,6 +1064,7 @@ dependencies = [ "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -1016,13 +1096,37 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "ed448-goldilocks" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" dependencies = [ - "fiat-crypto", + "fiat-crypto 0.1.20", "hex", "subtle", ] @@ -1120,6 +1224,12 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1451,7 +1561,16 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac 0.13.0", ] [[package]] @@ -1463,6 +1582,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "home" version = "0.5.12" @@ -1540,6 +1668,7 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ + "ctutils", "typenum", ] @@ -1846,6 +1975,25 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2029,16 +2177,84 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-dsa" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" +dependencies = [ + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", + "hybrid-array", + "module-lattice", + "pkcs8 0.11.0", + "shake", + "signature 3.0.0", +] + +[[package]] +name = "mlkem-rs" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0965b8b081668ff0398dc5e9dc3f2ebb9e833393f4ab5b9f725ddce11acef8" +dependencies = [ + "rand_core 0.6.4", + "serde", + "sha3", + "subtle", + "zeroize", +] + +[[package]] +name = "mlkem-tls" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b205d031298adf904d88efd6a57862d8650a4ab754aade19a9b5e87040bf4e" +dependencies = [ + "mlkem-rs", + "rand_core 0.6.4", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "mtp" version = "0.1.0" source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "mtp-client", + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-host", + "mtp-transport", + "mtp-type-map", +] + +[[package]] +name = "mtp-client" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" dependencies = [ "mtp-codec", "mtp-common", - "mtp-host", + "mtp-crypto", "mtp-transport", - "mtp-type-map", + "rand 0.8.6", + "tokio", ] [[package]] @@ -2049,6 +2265,7 @@ dependencies = [ "base64", "byteorder", "mtp-common", + "mtp-crypto", "mtp-type-map", "rand 0.8.6", ] @@ -2064,6 +2281,23 @@ dependencies = [ "wtransport", ] +[[package]] +name = "mtp-crypto" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "chacha20poly1305", + "ed25519-dalek", + "getrandom 0.4.3", + "hkdf 0.13.0", + "ml-dsa", + "mlkem-tls", + "rand_core 0.6.4", + "serde", + "sha2 0.11.0", + "zeroize", +] + [[package]] name = "mtp-host" version = "0.1.0" @@ -2071,6 +2305,7 @@ source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a43 dependencies = [ "mtp-codec", "mtp-common", + "mtp-crypto", "mtp-transport", "rand 0.8.6", "tokio", @@ -2198,9 +2433,10 @@ dependencies = [ "dashmap", "dotenv", "hex", - "hkdf", + "hkdf 0.12.4", "json", "mtp", + "mtp-crypto", "once_cell", "rand 0.8.6", "rand_core 0.6.4", @@ -2272,7 +2508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -2329,9 +2565,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] @@ -2340,8 +2576,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", ] [[package]] @@ -2386,6 +2632,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -2541,7 +2798,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20", + "chacha20 0.10.1", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -2726,10 +2983,10 @@ dependencies = [ "num-integer", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", - "signature", - "spki", + "signature 2.2.0", + "spki 0.7.3", "subtle", "zeroize", ] @@ -3048,6 +3305,27 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" @@ -3074,6 +3352,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -3157,9 +3445,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", ] +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.0", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + [[package]] name = "sqlx" version = "0.8.6" @@ -3266,8 +3570,8 @@ dependencies = [ "futures-util", "generic-array", "hex", - "hkdf", - "hmac", + "hkdf 0.12.4", + "hmac 0.12.1", "itoa", "log", "md-5", @@ -3304,8 +3608,8 @@ dependencies = [ "futures-core", "futures-util", "hex", - "hkdf", - "hmac", + "hkdf 0.12.4", + "hmac 0.12.1", "home", "itoa", "log", @@ -4246,6 +4550,18 @@ dependencies = [ "url", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "x448" version = "0.6.0" @@ -4416,7 +4732,7 @@ dependencies = [ "deflate64", "flate2", "getrandom 0.3.4", - "hmac", + "hmac 0.12.1", "indexmap", "lzma-rust2", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 5d4e97e..f227b9e 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,10 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/methanium/mtp", features = ["host"] } +mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ + "host", + "crypto", +] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" @@ -36,3 +39,4 @@ uuid = { version = "1.19.0", features = ["v4"] } x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" +mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = ["pqc"] } diff --git a/src/main.rs b/src/main.rs index 40bf10a..9429e59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,24 +8,30 @@ use crate::notifications::tauri; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::transport::omikron_connection; -use crate::util::crypto_helper::load_public_key; -use crate::util::crypto_helper::load_secret_key; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; +use base64::Engine as _; use dotenv::from_path; +use mtp_crypto::{Keyring, PublicKeyBundle}; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; -static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); -pub fn get_private_key() -> x448::Secret { - load_secret_key(&*PRIVATE_KEY).unwrap() +static KEYRING_ENV: Lazy = Lazy::new(|| env::var("KEYRING").unwrap()); +fn load_keyring() -> Keyring { + let bytes = base64::engine::general_purpose::STANDARD + .decode(&*KEYRING_ENV) + .expect("Invalid KEYRING env var: not valid base64"); + Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize") } -static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); -pub fn get_public_key() -> x448::PublicKey { - load_public_key(&*PUBLIC_KEY).unwrap() +pub fn get_keyring() -> &'static Keyring { + static KEYRING: Lazy = Lazy::new(load_keyring); + &KEYRING +} +pub fn get_public_key_bundle() -> PublicKeyBundle { + get_keyring().public_key_bundle() } #[tokio::main] diff --git a/src/server/api.rs b/src/server/api.rs index eb6f9cf..c9a1c59 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,16 +1,16 @@ -use crate::get_public_key; +use crate::get_public_key_bundle; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, - util::crypto_helper::public_key_to_base64, }; use actix_web::HttpResponse; use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; +use mtp_crypto::PublicKeyBundle; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { if path == "OPTIONS" { @@ -195,7 +195,8 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { ["api", "get", "public_key"] => { let mut res = JsonValue::new_object(); res["status"] = "success".into(); - res["public_key"] = public_key_to_base64(&get_public_key()).into(); + let bundle = get_public_key_bundle(); + res["public_key"] = base64::engine::general_purpose::STANDARD.encode(bundle.to_bytes()).into(); (StatusCode::OK, res.dump()) } diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs deleted file mode 100644 index 68e3855..0000000 --- a/src/util/crypto_helper.rs +++ /dev/null @@ -1,137 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, OsRng}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use rand_core::RngCore; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret, SharedSecret}; - -/// Errors for crypto operations -#[derive(Debug)] -pub enum CryptoError { - Base64Decode, - AgreementError, - EncryptionError(aes_gcm::Error), - DecryptionError(aes_gcm::Error), -} - -impl From for CryptoError { - fn from(_: base64::DecodeError) -> Self { - CryptoError::Base64Decode - } -} - -pub fn generate_keypair() -> (Secret, PublicKey) { - let mut buf = [0u8; 56]; - let mut rng = OsRng; - rng.fill_bytes(&mut buf); - let secret = Secret::from_bytes(&buf).unwrap(); - let public = PublicKey::from(&secret); - (secret, public) -} - -pub fn public_key_to_base64(pubkey: &PublicKey) -> String { - STANDARD.encode(pubkey.as_bytes().as_ref()) -} - -pub fn secret_key_to_base64(secret: &Secret) -> String { - STANDARD.encode(secret.as_bytes().as_ref()) -} - -pub fn load_public_key(base64_pub: &str) -> Option { - let bytes = STANDARD.decode(base64_pub).unwrap(); - PublicKey::from_bytes(&bytes) -} - -pub fn load_secret_key(base64_secret: &str) -> Option { - let bytes = STANDARD.decode(base64_secret).unwrap(); - Secret::from_bytes(&bytes) -} - -fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(shared.as_bytes()); - let result = hasher.finalize(); - let mut key = [0u8; 32]; - key.copy_from_slice(&result[..32]); - key -} - -pub fn encrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - plaintext: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - encrypt(secret, peer_pub, plaintext) -} -pub fn encrypt( - secret: Secret, - peer_pub: PublicKey, - plaintext: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - let mut nonce_bytes = [0u8; 12]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - let ciphertext = cipher - .encrypt(nonce, plaintext.as_bytes()) - .map_err(CryptoError::EncryptionError)?; - // prefix nonce to ciphertext - let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); - out.extend_from_slice(&nonce_bytes); - out.extend_from_slice(&ciphertext); - Ok(STANDARD.encode(&out)) -} - -pub fn decrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - encrypted_base64: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - decrypt(secret, peer_pub, encrypted_base64) -} -pub fn decrypt( - secret: Secret, - peer_pub: PublicKey, - encrypted_base64: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - - let encrypted = STANDARD.decode(encrypted_base64)?; - if encrypted.len() < 12 { - return Err(CryptoError::DecryptionError(aes_gcm::Error)); - } - let nonce_bytes = &encrypted[..12]; - let ciphertext = &encrypted[12..]; - let nonce = Nonce::from_slice(nonce_bytes); - let plaintext_bytes = cipher - .decrypt(nonce, ciphertext) - .map_err(CryptoError::DecryptionError)?; - let plaintext = String::from_utf8(plaintext_bytes) - .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; - Ok(plaintext) -} - -pub fn hash_it(input: &str) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(input.as_bytes()); - hasher.finalize().to_vec() -} - -pub fn hex_hash(input: &str) -> String { - let digest = hash_it(input); - digest.iter().map(|b| format!("{:02x}", b)).collect() -} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs deleted file mode 100644 index 97acab4..0000000 --- a/src/util/crypto_util.rs +++ /dev/null @@ -1,201 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, Payload}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; -use hkdf::Hkdf; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret}; - -// --- Custom Errors --- -#[derive(Debug)] -pub enum SecurePayloadError { - InvalidBase64, - InvalidHex, - EncryptionError, - DecryptionError, - InvalidKeyLength, -} - -// --- Data Format Enum --- -#[derive(Clone, Copy, Debug)] -pub enum DataFormat { - Raw, - Base64, - Hex, -} - -// --- Main Class Structure --- -pub struct SecurePayload { - /// The internal canonical representation is always raw bytes. - inner_data: Vec, - /// The private key of the user associated with this payload instance. - private_key: Secret, -} - -impl Clone for SecurePayload { - fn clone(&self) -> Self { - Self { - inner_data: self.inner_data.clone(), - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - } - } -} - -impl SecurePayload { - /// Clear Constructor: Takes data in any format and the user's private key. - pub fn new>( - data: T, - format: DataFormat, - private_key: S, - ) -> Result - where - S: Into, - { - let raw_data = match format { - DataFormat::Raw => data.as_ref().to_vec(), - DataFormat::Base64 => BASE64_STD - .decode(data.as_ref()) - .map_err(|_| SecurePayloadError::InvalidBase64)?, - DataFormat::Hex => { - hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? - } - }; - - Ok(Self { - inner_data: raw_data, - private_key: private_key.into(), - }) - } - - /// Helper to get the public key associated with this instance's private key. - pub fn get_public_key(&self) -> [u8; 56] { - *PublicKey::from(&self.private_key).as_bytes() - } - - /// Exports the internal data to the requested format - pub fn export(&self, format: DataFormat) -> String { - match format.into() { - DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), - DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), - DataFormat::Hex => hex::encode(&self.inner_data), - } - } - - /// Access raw bytes directly - pub fn get_bytes(&self) -> &[u8] { - &self.inner_data - } - - /// Returns the SHA-256 Hash of the data in the requested format - pub fn get_hash(&self, format: DataFormat) -> String { - let mut hasher = Sha256::new(); - hasher.update(&self.inner_data); - let result = hasher.finalize(); - - match format { - DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), - DataFormat::Base64 => BASE64_STD.encode(result), - DataFormat::Hex => hex::encode(result), - } - } - - /// Encrypts the held data for a specific recipient using AES-256-GCM. - /// The message will contain ONLY the ciphertext. - pub fn encrypt_x448(&self, public_key: S) -> Result - where - S: Into, - { - let peer_pub = public_key.into(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - println!( - "Encryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 3. Key & Nonce Derivation (HKDF) - // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - // 4. Encrypt with AES-256-GCM - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let ciphertext = cipher - .encrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - // 5. Result is ONLY the ciphertext. No key or nonce is packed. - Ok(SecurePayload { - inner_data: ciphertext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } - - /// Decrypts the held data providing the sender's public key manually. - pub fn decrypt_to_format( - &self, - peer_public_key_bytes: &[u8; 56], - output_format: DataFormat, - ) -> Result { - let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; - Ok(decrypted_instance.export(output_format)) - } - - /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. - pub fn decrypt_x448( - &self, - peer_public_key_bytes: &[u8; 56], - ) -> Result { - // 1. Perform Exchange - let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - // LOGGING: Shared Secret - println!( - "Decryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 2. Key & Nonce Derivation (Must match encryption exactly) - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - // 3. Decrypt with AES-256-GCM - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let plaintext = cipher - .decrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - Ok(SecurePayload { - inner_data: plaintext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs index 1897223..d0cc1dd 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,2 @@ -pub mod crypto_helper; -pub mod crypto_util; pub mod file_util; pub mod logger; From 10e43f6de1bfabf5a1736eeb48dfe5b820122217 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:19:12 +0200 Subject: [PATCH 161/220] [WIP] MTP migration --- Cargo.lock | 219 +++++++++++++++++++--------- Cargo.toml | 2 +- src/main.rs | 6 +- src/server/api.rs | 9 +- src/transport/omikron_connection.rs | 94 +++++++----- 5 files changed, 211 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e5fdac..36da87b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.10.1", + "rand 0.10.2", "sha1 0.11.0", "smallvec", "tokio", @@ -222,6 +222,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "aes" version = "0.8.4" @@ -229,19 +239,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] [[package]] -name = "aes-gcm" -version = "0.10.3" +name = "aes" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ - "aead", - "aes", - "cipher", + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" +dependencies = [ + "aead 0.6.1", + "aes 0.9.1", + "cipher 0.5.2", "ctr", "ghash", "subtle", @@ -313,7 +334,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -505,9 +526,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -516,14 +537,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -681,7 +703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] @@ -702,9 +724,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", + "aead 0.5.2", "chacha20 0.9.1", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -716,10 +738,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "cmake" version = "0.1.58" @@ -818,6 +851,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -899,11 +938,11 @@ dependencies = [ [[package]] name = "ctr" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher", + "cipher 0.5.2", ] [[package]] @@ -1441,11 +1480,10 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "opaque-debug", "polyval", ] @@ -1867,6 +1905,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -1911,7 +1958,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror", + "thiserror 2.0.18", "walkdir", "windows-link", ] @@ -2038,14 +2085,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "bitflags 2.13.0", "libc", "plain", - "redox_syscall 0.8.1", + "redox_syscall 0.9.0", ] [[package]] @@ -2233,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "mtp-client", "mtp-codec", @@ -2247,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "mtp-codec", "mtp-common", @@ -2260,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "base64", "byteorder", @@ -2273,18 +2320,18 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "quinn", "rustls", - "thiserror", + "thiserror 2.0.18", "wtransport", ] [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "chacha20poly1305", "ed25519-dalek", @@ -2295,13 +2342,14 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.11.0", + "thiserror 1.0.69", "zeroize", ] [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "mtp-codec", "mtp-common", @@ -2314,7 +2362,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "log", "mtp-codec", @@ -2328,7 +2376,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "serde", "serde_yaml", @@ -2346,9 +2394,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" dependencies = [ "num-integer", "num-traits", @@ -2447,7 +2495,7 @@ dependencies = [ "sqlx", "strum", "strum_macros", - "thiserror", + "thiserror 2.0.18", "tokio", "uuid", "x448", @@ -2640,19 +2688,18 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", ] [[package]] name = "polyval" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", + "cpubits", + "cpufeatures 0.3.0", + "universal-hash 0.6.1", ] [[package]] @@ -2708,7 +2755,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.4", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -2730,7 +2777,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -2794,9 +2841,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20 0.10.1", "getrandom 0.4.3", @@ -2877,9 +2924,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ "bitflags 2.13.0", ] @@ -2993,9 +3040,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -3081,9 +3128,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -3506,7 +3553,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror", + "thiserror 2.0.18", "tracing", "url", ] @@ -3586,7 +3633,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3623,7 +3670,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3647,7 +3694,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.18", "tracing", "url", ] @@ -3745,13 +3792,33 @@ dependencies = [ "libc", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3767,9 +3834,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.51" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", "num-conv", @@ -3787,9 +3854,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -4009,6 +4076,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -4529,7 +4606,7 @@ dependencies = [ "rustls-pki-types", "sha2 0.11.0", "socket2 0.6.4", - "thiserror", + "thiserror 2.0.18", "time", "tokio", "tracing", @@ -4546,7 +4623,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror", + "thiserror 2.0.18", "url", ] @@ -4587,7 +4664,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -4724,7 +4801,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" dependencies = [ - "aes", + "aes 0.8.4", "arbitrary", "bzip2", "constant_time_eq", @@ -4747,9 +4824,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" +checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index f227b9e..e9a78f4 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ "host", - "crypto", + "crypto" ] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } diff --git a/src/main.rs b/src/main.rs index 9429e59..c54fba3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,12 +26,8 @@ fn load_keyring() -> Keyring { .expect("Invalid KEYRING env var: not valid base64"); Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize") } -pub fn get_keyring() -> &'static Keyring { - static KEYRING: Lazy = Lazy::new(load_keyring); - &KEYRING -} pub fn get_public_key_bundle() -> PublicKeyBundle { - get_keyring().public_key_bundle() + load_keyring().public_key_bundle() } #[tokio::main] diff --git a/src/server/api.rs b/src/server/api.rs index c9a1c59..219d5fc 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,16 +1,13 @@ use crate::get_public_key_bundle; use crate::sql::sql; +use crate::sql::sql::{get_by_user_id, get_omikron_by_id}; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; -use crate::{ - sql::sql::{get_by_user_id, get_omikron_by_id}, -}; use actix_web::HttpResponse; use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; -use mtp_crypto::PublicKeyBundle; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { if path == "OPTIONS" { @@ -196,7 +193,9 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); let bundle = get_public_key_bundle(); - res["public_key"] = base64::engine::general_purpose::STANDARD.encode(bundle.to_bytes()).into(); + res["public_key"] = base64::engine::general_purpose::STANDARD + .encode(bundle.as_bytes()) + .into(); (StatusCode::OK, res.dump()) } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index cc53520..982db94 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,5 +1,5 @@ use crate::{ - get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, + get_keyring, get_public_key_bundle, log, log_cv_in, log_cv_out, log_err, log_in, server::short_link::add_short_link, sql::{ connection_status::UserStatus, @@ -7,14 +7,14 @@ use crate::{ user_online_tracker::{self}, }, transport::omikron_manager, - util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, + util::{file_util::load_file_vec, logger::PrintType}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, host::{HostConfig, MTPHost}}; use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; -use std::net::{IpAddr, Ipv4Addr}; use rand::{Rng, distributions::Alphanumeric}; +use std::net::{IpAddr, Ipv4Addr}; use std::{ sync::Arc, time::{Duration, Instant}, @@ -268,11 +268,12 @@ impl OmikronConnection { log!("Stored State"); - let challenge_clone = challenge.clone(); - let private_key = get_private_key(); + let challenge_clone: String = challenge.clone(); + let private_key = get_keyring(); let public_key_for_encrypt = omikron_pub_key; let encrypted = tokio::task::spawn_blocking(move || { + challenge_clone encrypt(private_key, public_key_for_encrypt, &challenge_clone) .map_err(|_| OmikronError::AuthenticationFailed) }) @@ -286,7 +287,7 @@ impl OmikronConnection { .with_id(cv.get_id()) .add_typed_default( DataType::PublicKey, - DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + DataValue::Str(STANDARD.encode(get_public_key_bundle().as_bytes())), ) .add_typed_default(DataType::Content, DataValue::Str(encrypted)); @@ -579,7 +580,10 @@ impl OmikronConnection { .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default(DataType::SubLevel, DataValue::SignedNumber(sub_level as i128)) + .add_typed_default( + DataType::SubLevel, + DataValue::SignedNumber(sub_level as i128), + ) .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); // Display name (fallback to username) @@ -594,7 +598,8 @@ impl OmikronConnection { response = response.add_typed_default(DataType::About, DataValue::Str(a)); } if let Some(av) = avatar { - response = response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); + response = + response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); } // Online status @@ -612,7 +617,10 @@ impl OmikronConnection { DataType::OnlineStatus, DataValue::Str(display_status.to_string()), ); - response = response.add_typed_default(DataType::OmikronId, DataValue::SignedNumber(us.omikron_id.into())); + response = response.add_typed_default( + DataType::OmikronId, + DataValue::SignedNumber(us.omikron_id.into()), + ); } else { response = response.add_typed_default( DataType::OnlineStatus, @@ -706,7 +714,8 @@ impl OmikronConnection { .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); if let Some(uid) = user_id { - response = response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); + response = + response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); } if let Some(uname) = username { response = response.add_typed_default(DataType::Username, DataValue::Str(uname)); @@ -730,7 +739,10 @@ impl OmikronConnection { let register_id = sql::get_register_id().await; let response = CommunicationValue::new(CommunicationType::GetRegister) .with_id(cv.get_id()) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(register_id as i128)); + .add_typed_default( + DataType::UserId, + DataValue::SignedNumber(register_id as i128), + ); self.send(&response).await } @@ -763,7 +775,10 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::CompleteRegisterIota) .with_id(cv.get_id()) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(new_iota_id.into())); + .add_typed_default( + DataType::IotaId, + DataValue::SignedNumber(new_iota_id.into()), + ); self.send(&response).await } Err(e) => { @@ -919,7 +934,10 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); + .add_typed_default( + DataType::ErrorType, + DataValue::Str(error_message), + ); self.send(&response).await } } else { @@ -986,8 +1004,14 @@ impl OmikronConnection { .map(|(sender, amount)| { let tm = mtp::type_map::TypeMap::latest(); DataValue::Container(vec![ - (DataType::SenderId.to_id(&tm), DataValue::SignedNumber(sender.into())), - (DataType::Amount.to_id(&tm), DataValue::SignedNumber(amount.into())), + ( + DataType::SenderId.to_id(&tm), + DataValue::SignedNumber(sender.into()), + ), + ( + DataType::Amount.to_id(&tm), + DataValue::SignedNumber(amount.into()), + ), ]) }) .collect(), @@ -1033,7 +1057,10 @@ impl OmikronConnection { // Sync with other Omikron clients let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(other_id.into())); + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(other_id.into()), + ); crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; } } @@ -1070,7 +1097,10 @@ impl OmikronConnection { // Sync with other Omikron clients let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id.into())); + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(sender_id.into()), + ); crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; } Ok(()) @@ -1099,7 +1129,10 @@ impl OmikronConnection { }; let tm = mtp::type_map::TypeMap::latest(); let mut map = Vec::new(); - map.push((DataType::UserId.to_id(&tm), DataValue::SignedNumber(user_id.into()))); + map.push(( + DataType::UserId.to_id(&tm), + DataValue::SignedNumber(user_id.into()), + )); map.push((DataType::UserState.to_id(&tm), DataValue::Str(status_str))); states.push(DataValue::Container(map)); } @@ -1192,29 +1225,16 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = host( - IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), + let mut host: MTPHost = MTPHost::new( + HostConfig::new( + IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, cert_pem, key_pem, - Policy { - send_mode: SendMode::SingleStreamPerMessage, - max_message_size: 1_000_000_000, - close_frame_len: u32::MAX, - application_close_code: 0, - open_stream_timeout: Duration::from_millis(2_000), - write_timeout: Duration::from_millis(2_000), - accept_stream_timeout: Duration::from_millis(10_000), - read_timeout: Duration::from_millis(30_000), - keep_alive_interval: Some(Duration::from_secs(6)), - max_idle_timeout: Some(Duration::from_secs(30)), - force_close_delay: Duration::from_millis(300), - max_transient_recv_errors: 20, - transient_recv_backoff: Duration::from_millis(100), - receiver_queue_capacity: 1000, - }, + ) ) .await?; + log!("OmikronServer listening on port {}", port); while let Some((sender, mut receiver)) = host.next().await { From 607bf337fe12e711922792d388eb1c523b361cf3 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 16:19:12 +0200 Subject: [PATCH 162/220] [WIP] MTP migration --- Cargo.lock | 219 +++++++++++++++++++--------- Cargo.toml | 2 +- src/main.rs | 6 +- src/server/api.rs | 9 +- src/transport/omikron_connection.rs | 94 +++++++----- 5 files changed, 211 insertions(+), 119 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e5fdac..36da87b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,7 +50,7 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite", - "rand 0.10.1", + "rand 0.10.2", "sha1 0.11.0", "smallvec", "tokio", @@ -222,6 +222,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aead" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" +dependencies = [ + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "aes" version = "0.8.4" @@ -229,19 +239,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] [[package]] -name = "aes-gcm" -version = "0.10.3" +name = "aes" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" dependencies = [ - "aead", - "aes", - "cipher", + "cipher 0.5.2", + "cpubits", + "cpufeatures 0.3.0", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" +dependencies = [ + "aead 0.6.1", + "aes 0.9.1", + "cipher 0.5.2", "ctr", "ghash", "subtle", @@ -313,7 +334,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -505,9 +526,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" +checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -516,14 +537,15 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" dependencies = [ "cc", "cmake", "dunce", "fs_extra", + "pkg-config", ] [[package]] @@ -681,7 +703,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher", + "cipher 0.4.4", "cpufeatures 0.2.17", ] @@ -702,9 +724,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead", + "aead 0.5.2", "chacha20 0.9.1", - "cipher", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -716,10 +738,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout", + "inout 0.1.4", "zeroize", ] +[[package]] +name = "cipher" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "inout 0.2.2", +] + [[package]] name = "cmake" version = "0.1.58" @@ -818,6 +851,12 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cpubits" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -899,11 +938,11 @@ dependencies = [ [[package]] name = "ctr" -version = "0.9.2" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher", + "cipher 0.5.2", ] [[package]] @@ -1441,11 +1480,10 @@ dependencies = [ [[package]] name = "ghash" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "opaque-debug", "polyval", ] @@ -1867,6 +1905,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inout" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" +dependencies = [ + "hybrid-array", +] + [[package]] name = "instant" version = "0.1.13" @@ -1911,7 +1958,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror", + "thiserror 2.0.18", "walkdir", "windows-link", ] @@ -2038,14 +2085,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ "bitflags 2.13.0", "libc", "plain", - "redox_syscall 0.8.1", + "redox_syscall 0.9.0", ] [[package]] @@ -2233,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "mtp-client", "mtp-codec", @@ -2247,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "mtp-codec", "mtp-common", @@ -2260,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "base64", "byteorder", @@ -2273,18 +2320,18 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "quinn", "rustls", - "thiserror", + "thiserror 2.0.18", "wtransport", ] [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "chacha20poly1305", "ed25519-dalek", @@ -2295,13 +2342,14 @@ dependencies = [ "rand_core 0.6.4", "serde", "sha2 0.11.0", + "thiserror 1.0.69", "zeroize", ] [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "mtp-codec", "mtp-common", @@ -2314,7 +2362,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "log", "mtp-codec", @@ -2328,7 +2376,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" dependencies = [ "serde", "serde_yaml", @@ -2346,9 +2394,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" dependencies = [ "num-integer", "num-traits", @@ -2447,7 +2495,7 @@ dependencies = [ "sqlx", "strum", "strum_macros", - "thiserror", + "thiserror 2.0.18", "tokio", "uuid", "x448", @@ -2640,19 +2688,18 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash", + "universal-hash 0.5.1", ] [[package]] name = "polyval" -version = "0.6.2" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", + "cpubits", + "cpufeatures 0.3.0", + "universal-hash 0.6.1", ] [[package]] @@ -2708,7 +2755,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2 0.6.4", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -2730,7 +2777,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -2794,9 +2841,9 @@ dependencies = [ [[package]] name = "rand" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20 0.10.1", "getrandom 0.4.3", @@ -2877,9 +2924,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.8.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b44b894f2a6e36457d665d1e08c3866add6ed5e70050c1b4ba8a8ddedb02ce7" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ "bitflags 2.13.0", ] @@ -2993,9 +3040,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "2.1.2" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" [[package]] name = "rustc_version" @@ -3081,9 +3128,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ "web-time", "zeroize", @@ -3506,7 +3553,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror", + "thiserror 2.0.18", "tracing", "url", ] @@ -3586,7 +3633,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3623,7 +3670,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.18", "tracing", "whoami", ] @@ -3647,7 +3694,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.18", "tracing", "url", ] @@ -3745,13 +3792,33 @@ dependencies = [ "libc", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -3767,9 +3834,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.51" +version = "0.3.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" dependencies = [ "deranged", "num-conv", @@ -3787,9 +3854,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.30" +version = "0.2.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" dependencies = [ "num-conv", "time-core", @@ -4009,6 +4076,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "universal-hash" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" +dependencies = [ + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -4529,7 +4606,7 @@ dependencies = [ "rustls-pki-types", "sha2 0.11.0", "socket2 0.6.4", - "thiserror", + "thiserror 2.0.18", "time", "tokio", "tracing", @@ -4546,7 +4623,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror", + "thiserror 2.0.18", "url", ] @@ -4587,7 +4664,7 @@ dependencies = [ "nom", "oid-registry", "rusticata-macros", - "thiserror", + "thiserror 2.0.18", "time", ] @@ -4724,7 +4801,7 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" dependencies = [ - "aes", + "aes 0.8.4", "arbitrary", "bzip2", "constant_time_eq", @@ -4747,9 +4824,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "977347db8caa080403f6b6b7c1cda9479a8e869316f7e13a59b19076a40f94e3" +checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index f227b9e..e9a78f4 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ "host", - "crypto", + "crypto" ] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } diff --git a/src/main.rs b/src/main.rs index 9429e59..c54fba3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,12 +26,8 @@ fn load_keyring() -> Keyring { .expect("Invalid KEYRING env var: not valid base64"); Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize") } -pub fn get_keyring() -> &'static Keyring { - static KEYRING: Lazy = Lazy::new(load_keyring); - &KEYRING -} pub fn get_public_key_bundle() -> PublicKeyBundle { - get_keyring().public_key_bundle() + load_keyring().public_key_bundle() } #[tokio::main] diff --git a/src/server/api.rs b/src/server/api.rs index c9a1c59..219d5fc 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,16 +1,13 @@ use crate::get_public_key_bundle; use crate::sql::sql; +use crate::sql::sql::{get_by_user_id, get_omikron_by_id}; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; -use crate::{ - sql::sql::{get_by_user_id, get_omikron_by_id}, -}; use actix_web::HttpResponse; use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; -use mtp_crypto::PublicKeyBundle; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { if path == "OPTIONS" { @@ -196,7 +193,9 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); let bundle = get_public_key_bundle(); - res["public_key"] = base64::engine::general_purpose::STANDARD.encode(bundle.to_bytes()).into(); + res["public_key"] = base64::engine::general_purpose::STANDARD + .encode(bundle.as_bytes()) + .into(); (StatusCode::OK, res.dump()) } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index cc53520..982db94 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,5 +1,5 @@ use crate::{ - get_private_key, get_public_key, log, log_cv_in, log_cv_out, log_err, log_in, + get_keyring, get_public_key_bundle, log, log_cv_in, log_cv_out, log_err, log_in, server::short_link::add_short_link, sql::{ connection_status::UserStatus, @@ -7,14 +7,14 @@ use crate::{ user_online_tracker::{self}, }, transport::omikron_manager, - util::{crypto_helper::encrypt, file_util::load_file_vec, logger::PrintType}, + util::{file_util::load_file_vec, logger::PrintType}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, host::{HostConfig, MTPHost}}; use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; -use std::net::{IpAddr, Ipv4Addr}; use rand::{Rng, distributions::Alphanumeric}; +use std::net::{IpAddr, Ipv4Addr}; use std::{ sync::Arc, time::{Duration, Instant}, @@ -268,11 +268,12 @@ impl OmikronConnection { log!("Stored State"); - let challenge_clone = challenge.clone(); - let private_key = get_private_key(); + let challenge_clone: String = challenge.clone(); + let private_key = get_keyring(); let public_key_for_encrypt = omikron_pub_key; let encrypted = tokio::task::spawn_blocking(move || { + challenge_clone encrypt(private_key, public_key_for_encrypt, &challenge_clone) .map_err(|_| OmikronError::AuthenticationFailed) }) @@ -286,7 +287,7 @@ impl OmikronConnection { .with_id(cv.get_id()) .add_typed_default( DataType::PublicKey, - DataValue::Str(STANDARD.encode(get_public_key().as_bytes())), + DataValue::Str(STANDARD.encode(get_public_key_bundle().as_bytes())), ) .add_typed_default(DataType::Content, DataValue::Str(encrypted)); @@ -579,7 +580,10 @@ impl OmikronConnection { .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default(DataType::SubLevel, DataValue::SignedNumber(sub_level as i128)) + .add_typed_default( + DataType::SubLevel, + DataValue::SignedNumber(sub_level as i128), + ) .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); // Display name (fallback to username) @@ -594,7 +598,8 @@ impl OmikronConnection { response = response.add_typed_default(DataType::About, DataValue::Str(a)); } if let Some(av) = avatar { - response = response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); + response = + response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); } // Online status @@ -612,7 +617,10 @@ impl OmikronConnection { DataType::OnlineStatus, DataValue::Str(display_status.to_string()), ); - response = response.add_typed_default(DataType::OmikronId, DataValue::SignedNumber(us.omikron_id.into())); + response = response.add_typed_default( + DataType::OmikronId, + DataValue::SignedNumber(us.omikron_id.into()), + ); } else { response = response.add_typed_default( DataType::OnlineStatus, @@ -706,7 +714,8 @@ impl OmikronConnection { .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); if let Some(uid) = user_id { - response = response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); + response = + response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); } if let Some(uname) = username { response = response.add_typed_default(DataType::Username, DataValue::Str(uname)); @@ -730,7 +739,10 @@ impl OmikronConnection { let register_id = sql::get_register_id().await; let response = CommunicationValue::new(CommunicationType::GetRegister) .with_id(cv.get_id()) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(register_id as i128)); + .add_typed_default( + DataType::UserId, + DataValue::SignedNumber(register_id as i128), + ); self.send(&response).await } @@ -763,7 +775,10 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::CompleteRegisterIota) .with_id(cv.get_id()) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(new_iota_id.into())); + .add_typed_default( + DataType::IotaId, + DataValue::SignedNumber(new_iota_id.into()), + ); self.send(&response).await } Err(e) => { @@ -919,7 +934,10 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::ErrorInternal) .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); + .add_typed_default( + DataType::ErrorType, + DataValue::Str(error_message), + ); self.send(&response).await } } else { @@ -986,8 +1004,14 @@ impl OmikronConnection { .map(|(sender, amount)| { let tm = mtp::type_map::TypeMap::latest(); DataValue::Container(vec![ - (DataType::SenderId.to_id(&tm), DataValue::SignedNumber(sender.into())), - (DataType::Amount.to_id(&tm), DataValue::SignedNumber(amount.into())), + ( + DataType::SenderId.to_id(&tm), + DataValue::SignedNumber(sender.into()), + ), + ( + DataType::Amount.to_id(&tm), + DataValue::SignedNumber(amount.into()), + ), ]) }) .collect(), @@ -1033,7 +1057,10 @@ impl OmikronConnection { // Sync with other Omikron clients let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(other_id.into())); + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(other_id.into()), + ); crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; } } @@ -1070,7 +1097,10 @@ impl OmikronConnection { // Sync with other Omikron clients let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id.into())); + .add_typed_default( + DataType::SenderId, + DataValue::SignedNumber(sender_id.into()), + ); crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; } Ok(()) @@ -1099,7 +1129,10 @@ impl OmikronConnection { }; let tm = mtp::type_map::TypeMap::latest(); let mut map = Vec::new(); - map.push((DataType::UserId.to_id(&tm), DataValue::SignedNumber(user_id.into()))); + map.push(( + DataType::UserId.to_id(&tm), + DataValue::SignedNumber(user_id.into()), + )); map.push((DataType::UserState.to_id(&tm), DataValue::Str(status_str))); states.push(DataValue::Container(map)); } @@ -1192,29 +1225,16 @@ pub async fn start(port: u16) -> Result<(), Box> { let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: Host = host( - IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), + let mut host: MTPHost = MTPHost::new( + HostConfig::new( + IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, cert_pem, key_pem, - Policy { - send_mode: SendMode::SingleStreamPerMessage, - max_message_size: 1_000_000_000, - close_frame_len: u32::MAX, - application_close_code: 0, - open_stream_timeout: Duration::from_millis(2_000), - write_timeout: Duration::from_millis(2_000), - accept_stream_timeout: Duration::from_millis(10_000), - read_timeout: Duration::from_millis(30_000), - keep_alive_interval: Some(Duration::from_secs(6)), - max_idle_timeout: Some(Duration::from_secs(30)), - force_close_delay: Duration::from_millis(300), - max_transient_recv_errors: 20, - transient_recv_backoff: Duration::from_millis(100), - receiver_queue_capacity: 1000, - }, + ) ) .await?; + log!("OmikronServer listening on port {}", port); while let Some((sender, mut receiver)) = host.next().await { From 18817bdad479fcdd7e0f79a3ea573610f56c9a44 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:57:31 +0200 Subject: [PATCH 163/220] MTP migration --- Cargo.lock | 17 +- src/main.rs | 10 - src/notifications/mod.rs | 1 - src/notifications/tauri.rs | 157 --------------- src/server/api.rs | 12 +- src/sql/sql.rs | 63 +++--- src/transport/omikron_connection.rs | 288 +++++++--------------------- 7 files changed, 123 insertions(+), 425 deletions(-) delete mode 100644 src/notifications/mod.rs delete mode 100644 src/notifications/tauri.rs diff --git a/Cargo.lock b/Cargo.lock index 36da87b..592912f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "mtp-client", "mtp-codec", @@ -2294,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "base64", "byteorder", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "quinn", "rustls", @@ -2331,8 +2331,9 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ + "base64", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", @@ -2349,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "mtp-codec", "mtp-common", @@ -2362,7 +2363,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "log", "mtp-codec", @@ -2376,7 +2377,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "serde", "serde_yaml", diff --git a/src/main.rs b/src/main.rs index c54fba3..8672167 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ -mod notifications; mod server; mod sql; mod transport; mod util; -use crate::notifications::tauri; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::transport::omikron_connection; @@ -53,13 +51,6 @@ async fn main() { } }); - let tauri_handle = tokio::spawn(async move { - match tauri::start(9189).await { - Err(e) => log_err!(0, PrintType::General, "{:?}", e), - _ => {} - } - }); - log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { @@ -94,5 +85,4 @@ async fn main() { } omikron_handle.abort(); - tauri_handle.abort(); } diff --git a/src/notifications/mod.rs b/src/notifications/mod.rs deleted file mode 100644 index 62411f7..0000000 --- a/src/notifications/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod tauri; diff --git a/src/notifications/tauri.rs b/src/notifications/tauri.rs deleted file mode 100644 index 8195988..0000000 --- a/src/notifications/tauri.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::log; -use crate::util::file_util::load_file_vec; -use crate::util::logger::PrintType; -use dashmap::DashMap; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; -use once_cell::sync::Lazy; -use std::net::{IpAddr, Ipv4Addr}; -use std::sync::Arc; -use std::time::Duration; - -pub struct TauriConnection { - pub user_id: i64, - pub sender: Arc, -} - -static TAURI_CONNECTIONS: Lazy>>> = Lazy::new(DashMap::new); - -pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - - let mut host: Host = host( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - port, - cert_pem, - key_pem, - Policy { - send_mode: SendMode::SingleStreamPerMessage, - max_message_size: 1_000_000_000, - close_frame_len: u32::MAX, - application_close_code: 0, - open_stream_timeout: Duration::from_millis(2_000), - write_timeout: Duration::from_millis(2_000), - accept_stream_timeout: Duration::from_millis(10_000), - read_timeout: Duration::from_millis(30_000), - keep_alive_interval: Some(Duration::from_secs(6)), - max_idle_timeout: Some(Duration::from_secs(30)), - force_close_delay: Duration::from_millis(300), - max_transient_recv_errors: 20, - transient_recv_backoff: Duration::from_millis(100), - receiver_queue_capacity: 1000, - }, - ) - .await?; - - log!("TauriServer listening on port {}", port); - - while let Some((sender, mut receiver)) = host.next().await { - tokio::spawn(async move { - handle_connection(sender, &mut receiver).await; - }); - } - - Ok(()) -} - -async fn handle_connection(sender: Sender, receiver: &mut Receiver) { - let mut current_user_id = 0; - let sender = Arc::new(sender); - - while let Ok(cv) = receiver.receive().await { - if cv.is_type(CommunicationType::TauriIdentification) { - let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; - if user_id != 0 { - current_user_id = user_id; - let conn = Arc::new(TauriConnection { - user_id, - sender: sender.clone(), - }); - - TAURI_CONNECTIONS - .entry(user_id) - .and_modify(|conns| { - conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); - conns.push(conn.clone()); - }) - .or_insert_with(|| vec![conn]); - - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - if let Err(e) = sender.send(&response).await { - log!( - PrintType::General, - "Failed to send tauri success response: {}", - e - ); - } else { - log!(user_id, PrintType::Client, "Tauri device registered"); - } - } - } else if cv.is_type(CommunicationType::Ping) { - let response = - CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); - if let Err(_) = sender.send(&response).await { - break; - } - } - } - - // Cleanup - if current_user_id != 0 { - if let Some(mut conns) = TAURI_CONNECTIONS.get_mut(¤t_user_id) { - conns.retain(|c| !Arc::ptr_eq(c.sender.handle(), sender.handle())); - } - } -} - -pub async fn send_notification(user_id: i64, sender_id: i64) { - let conns_opt = { - let entry = TAURI_CONNECTIONS.get(&user_id); - entry.map(|e| e.clone()) - }; - - if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::PushNotification) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); - - let mut remove_needed = false; - for conn in conns.iter() { - if conn.sender.send(&cv).await.is_err() { - remove_needed = true; - } - } - - if remove_needed { - if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { - conns_mut.retain(|conn| conn.sender.is_open()); - } - } - } -} - -pub async fn remove_notification(user_id: i64, sender_id: i64) { - let conns_opt = { - let entry = TAURI_CONNECTIONS.get(&user_id); - entry.map(|e| e.clone()) - }; - - if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::ReadNotification) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); - - let mut remove_needed = false; - for conn in conns.iter() { - if conn.sender.send(&cv).await.is_err() { - remove_needed = true; - } - } - - if remove_needed { - if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { - conns_mut.retain(|conn| conn.sender.is_open()); - } - } - } -} diff --git a/src/server/api.rs b/src/server/api.rs index 219d5fc..93c594e 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -68,7 +68,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) @@ -103,7 +103,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { @@ -111,7 +111,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) } else { @@ -126,7 +126,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) } else { @@ -172,7 +172,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["username"] = username.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["user_id"] = id.into(); res["iota_id"] = iota_id.into(); res["sub_level"] = sub_level.into(); @@ -227,7 +227,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["username"] = username.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["user_id"] = id.into(); res["iota_id"] = iota_id.into(); res["sub_level"] = sub_level.into(); diff --git a/src/sql/sql.rs b/src/sql/sql.rs index d9eb712..45b1899 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -1,4 +1,5 @@ use crate::log; +use mtp_crypto::PublicKeyBundle; use once_cell::sync::Lazy; use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -61,7 +62,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { avatar MEDIUMBLOB, sub_level INT(11) NOT NULL DEFAULT 0, sub_end BIGINT(20) NOT NULL DEFAULT 0, - public_key TEXT NOT NULL COLLATE utf8mb4_bin, + public_key BLOB NOT NULL, private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', iota_id BIGINT NOT NULL, token VARCHAR(256) NOT NULL UNIQUE COLLATE utf8mb4_bin @@ -73,7 +74,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS iotas ( id BIGINT NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + public_key BLOB NOT NULL )", ) .execute(&pool) @@ -82,7 +83,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS omikrons ( id BIGINT NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + public_key BLOB NOT NULL, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", @@ -160,7 +161,7 @@ pub async fn get_by_username( Option>, i32, i64, - String, + PublicKeyBundle, String, String, ), @@ -192,7 +193,8 @@ pub async fn get_by_username( let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: Vec = row.get("public_key"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -206,7 +208,7 @@ pub async fn get_by_username( avatar, sub_level, sub_end, - String::from_utf8_lossy(&public_key).to_string(), + public_key, String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) @@ -228,7 +230,7 @@ pub async fn get_by_user_id( Option>, i32, i64, - String, + PublicKeyBundle, String, String, ), @@ -260,7 +262,8 @@ pub async fn get_by_user_id( let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: Vec = row.get("public_key"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -274,7 +277,7 @@ pub async fn get_by_user_id( avatar, sub_level, sub_end, - String::from_utf8_lossy(&public_key).to_string(), + public_key, String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) @@ -296,7 +299,7 @@ pub async fn get_users_by_iota_id( Option>, i32, i64, - String, + PublicKeyBundle, String, String, )>, @@ -328,7 +331,8 @@ pub async fn get_users_by_iota_id( let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: Vec = row.get("public_key"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -342,7 +346,7 @@ pub async fn get_users_by_iota_id( avatar, sub_level, sub_end, - String::from_utf8_lossy(&public_key).to_string(), + public_key, String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )); @@ -477,7 +481,7 @@ pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error pub async fn change_keys( id: i64, - new_public_key: String, + new_public_key: PublicKeyBundle, new_private_key_hash: String, ) -> Result<(), sqlx::Error> { let pool = { @@ -489,7 +493,7 @@ pub async fn change_keys( }; sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key.as_bytes().to_vec()) + .bind(new_public_key.as_bytes()) .bind(new_private_key_hash.as_bytes().to_vec()) .bind(id) .execute(&pool) @@ -517,7 +521,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> pub async fn register_complete_user( id: i64, username: String, - public_key: String, + public_key: PublicKeyBundle, iota_id: i64, token: String, ) -> Result<(), sqlx::Error> { @@ -534,7 +538,7 @@ pub async fn register_complete_user( ) .bind(id) .bind(username.as_bytes().to_vec()) - .bind(public_key.as_bytes().to_vec()) + .bind(public_key.as_bytes()) .bind(iota_id) .bind(token.as_bytes().to_vec()) .execute(&pool) @@ -589,13 +593,13 @@ pub async fn print_users() -> Result<(), Box> { // ========================================================================================== // IOTA // ========================================================================================== -pub async fn create_new_iota(public_key: String) -> Result { +pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { let new_id = get_register_id().await as i64; register_complete_iota(new_id, public_key).await?; Ok(new_id) } -pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { +pub async fn register_complete_iota(id: i64, public_key: PublicKeyBundle) -> Result<(), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -606,14 +610,14 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id) - .bind(public_key.as_bytes().to_vec()) + .bind(public_key.as_bytes()) .execute(&pool) .await?; Ok(()) } -pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { +pub async fn get_iota_by_id(id: i64) -> Result<(i64, PublicKeyBundle), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -631,7 +635,9 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { match result { Ok(optional_row) => match optional_row { Some((id_i64, public_key)) => { - Ok((id_i64, String::from_utf8_lossy(&public_key).to_string())) + let bundle = PublicKeyBundle::from_bytes(&public_key) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + Ok((id_i64, bundle)) } _ => Err(sqlx::Error::RowNotFound), }, @@ -639,7 +645,7 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { } } -pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { +pub async fn change_iota_key(id: i64, new_key: PublicKeyBundle) -> Result<(), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -649,7 +655,7 @@ pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error }; sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(new_key.as_bytes().to_vec()) + .bind(new_key.as_bytes()) .bind(id) .execute(&pool) .await?; @@ -678,7 +684,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { // OMIKRONS // ========================================================================================== -pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { +pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -695,10 +701,11 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> .await?; match row { - Some((public_key, ip_address)) => Ok(( - String::from_utf8_lossy(&public_key).to_string(), - String::from_utf8_lossy(&ip_address).to_string(), - )), + Some((public_key, ip_address)) => { + let bundle = PublicKeyBundle::from_bytes(&public_key) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + Ok((bundle, String::from_utf8_lossy(&ip_address).to_string())) + } _ => Err(sqlx::Error::RowNotFound), } } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 982db94..cb11548 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,9 +1,9 @@ use crate::{ - get_keyring, get_public_key_bundle, log, log_cv_in, log_cv_out, log_err, log_in, + load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server::short_link::add_short_link, sql::{ connection_status::UserStatus, - sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, + sql::{self, get_by_user_id, get_by_username, get_iota_by_id}, user_online_tracker::{self}, }, transport::omikron_manager, @@ -11,9 +11,12 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, host::{HostConfig, MTPHost}}; -use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; -use rand::{Rng, distributions::Alphanumeric}; +use mtp::host::{AuthenticationPolicy, Receiver, Sender}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + host::{Host, HostConfig, Policy, SendMode}, +}; +use mtp_crypto::PublicKeyBundle; use std::net::{IpAddr, Ipv4Addr}; use std::{ sync::Arc, @@ -23,8 +26,6 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; -use x448::PublicKey; - // ============================================================================ // Configuration // ============================================================================ @@ -63,35 +64,6 @@ pub struct WaitingTask { pub inserted_at: Instant, } -// ============================================================================ -// Connection State -// ============================================================================ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum AuthState { - Unauthenticated, - Identified { omikron_id: i64 }, - Authenticated { omikron_id: i64 }, -} - -impl AuthState { - fn is_authenticated(&self) -> bool { - match self { - AuthState::Authenticated { omikron_id } => true, - _ => false, - } - } - - fn omikron_id(&self) -> Option { - match self { - AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { - Some(*omikron_id) - } - _ => None, - } - } -} - // ============================================================================ // Omikron Connection (mtp/QUIC-based) // ============================================================================ @@ -99,7 +71,6 @@ impl AuthState { pub struct OmikronConnection { id: u64, sender: Mutex>, - state: RwLock, challenge: RwLock, pub_key: RwLock>>, pub ping: RwLock, @@ -124,7 +95,6 @@ impl OmikronConnection { let conn = Arc::new(Self { id: rand::random(), sender: Mutex::new(Some(sender)), - state: RwLock::new(AuthState::Unauthenticated), challenge: RwLock::new(String::new()), pub_key: RwLock::new(None), ping: RwLock::new(-1), @@ -197,144 +167,9 @@ impl OmikronConnection { if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } - - let current_state = *self.state.read().await; - // Route based on authentication state - match current_state { - AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, - AuthState::Identified { .. } => self.clone().handle_identified(cv).await, - AuthState::Authenticated { omikron_id } => { - self.clone().handle_authenticated(cv, omikron_id).await - } - } + return Ok(()); } - // ------------------------------------------------------------------------- - // Authentication Handlers - // ------------------------------------------------------------------------- - - async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::Identification) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - // Extract omikron ID - let omikron_id = cv - .get_data(DataType::OmikronId) - .as_number() - .ok_or(OmikronError::InvalidResponse)?; - log!("Omikron {:?} connected", omikron_id); - - // Lookup omikron in database - let (public_key, _) = get_omikron_by_id(omikron_id as i64) - .await - .map_err(|e| OmikronError::Sql(e.to_string()))?; - - log!("Got public Key"); - - let pub_key_bytes = STANDARD - .decode(&public_key) - .map_err(|_| OmikronError::AuthenticationFailed)?; - - let pub_key_bytes_clone = pub_key_bytes.clone(); - let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) - .ok_or(OmikronError::AuthenticationFailed)?; - - log!("Decoded public Key"); - - // Generate challenge - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - log!("Generated Challenge"); - - *self.challenge.write().await = challenge.clone(); - - log!("Stored Challenge"); - - *self.pub_key.write().await = Some(pub_key_bytes); - - log!("Stored Pubkey"); - - *self.state.write().await = AuthState::Identified { - omikron_id: omikron_id as i64, - }; - - log!("Stored State"); - - let challenge_clone: String = challenge.clone(); - let private_key = get_keyring(); - let public_key_for_encrypt = omikron_pub_key; - - let encrypted = tokio::task::spawn_blocking(move || { - challenge_clone - encrypt(private_key, public_key_for_encrypt, &challenge_clone) - .map_err(|_| OmikronError::AuthenticationFailed) - }) - .await - .map_err(|_| OmikronError::AuthenticationFailed)??; - - log!("Encrypted Challenge"); - - // Send challenge response - let response = CommunicationValue::new(CommunicationType::Challenge) - .with_id(cv.get_id()) - .add_typed_default( - DataType::PublicKey, - DataValue::Str(STANDARD.encode(get_public_key_bundle().as_bytes())), - ) - .add_typed_default(DataType::Content, DataValue::Str(encrypted)); - - log!("Sending Challenge"); - self.send(&response).await - } - - async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::ChallengeResponse) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - let client_response = cv - .get_data(DataType::Content) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let expected_challenge = self.challenge.read().await.clone(); - - if client_response == expected_challenge { - let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); - *self.state.write().await = AuthState::Authenticated { omikron_id }; - - omikron_manager::add_omikron(self.clone()).await; - - let response = CommunicationValue::new(CommunicationType::IdentificationResponse) - .with_id(cv.get_id()) - .add_typed_default(DataType::Accepted, DataValue::Bool(true)); - - self.clone().send(&response).await?; - log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); - Ok(()) - } else { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::ErrorInvalidChallenge) - .await; - Err(OmikronError::AuthenticationFailed) - } - } - - // ------------------------------------------------------------------------- - // Authenticated Message Handlers - // ------------------------------------------------------------------------- - async fn handle_authenticated( self: Arc, cv: CommunicationValue, @@ -554,7 +389,7 @@ impl OmikronConnection { Option>, i32, i64, - String, + PublicKeyBundle, String, String, ), @@ -577,7 +412,7 @@ impl OmikronConnection { let mut response = CommunicationValue::new(CommunicationType::GetUserData) .with_id(msg_id) .add_typed_default(DataType::Username, DataValue::Str(username.clone())) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default( @@ -704,13 +539,13 @@ impl OmikronConnection { self: Arc, msg_id: u32, iota_id: i64, - public_key: String, + public_key: PublicKeyBundle, user_id: Option, username: Option, ) -> CommunicationValue { let mut response = CommunicationValue::new(CommunicationType::GetIotaData) .with_id(msg_id) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); if let Some(uid) = user_id { @@ -752,10 +587,15 @@ impl OmikronConnection { ) -> OmikronResult<()> { let iota_id_opt = cv.get_data(DataType::IotaId).as_number().map(|n| n as i64); - if let Some(public_key) = cv.get_data(DataType::PublicKey).as_str() { + let public_key = cv + .get_data(DataType::PublicKey) + .as_str() + .and_then(|s| PublicKeyBundle::from_base64(s).ok()); + + if let Some(public_key) = public_key { if let Some(iota_id) = iota_id_opt { // Register existing IOTA - match sql::register_complete_iota(iota_id, public_key.to_string()).await { + match sql::register_complete_iota(iota_id, public_key).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::Success) .with_id(cv.get_id()); @@ -770,7 +610,7 @@ impl OmikronConnection { } } else { // Create new IOTA - match sql::create_new_iota(public_key.to_string()).await { + match sql::create_new_iota(public_key).await { Ok(new_iota_id) => { let response = CommunicationValue::new(CommunicationType::CompleteRegisterIota) @@ -807,7 +647,7 @@ impl OmikronConnection { let public_key = cv .get_data(DataType::PublicKey) .as_str() - .map(|s| s.to_string()); + .and_then(|s| PublicKeyBundle::from_base64(s).ok()); let iota_id = cv.get_sender(); let reset_token = cv .get_data(DataType::ResetToken) @@ -873,15 +713,13 @@ impl OmikronConnection { } } if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataType::PublicKey).as_str(), + cv.get_data(DataType::PublicKey) + .as_str() + .and_then(|s| PublicKeyBundle::from_base64(s).ok()), cv.get_data(DataType::PrivateKeyHash).as_str(), ) { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await + if let Err(e) = + sql::change_keys(user_id, public_key, private_key_hash.to_string()).await { success = false; error_message = e.to_string(); @@ -1051,9 +889,6 @@ impl OmikronConnection { .with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with Tauri - crate::notifications::tauri::remove_notification(receiver_id, other_id).await; - // Sync with other Omikron clients let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) @@ -1091,9 +926,6 @@ impl OmikronConnection { CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with Tauri - crate::notifications::tauri::send_notification(receiver_id, sender_id).await; - // Sync with other Omikron clients let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) @@ -1189,12 +1021,10 @@ impl OmikronConnection { } async fn cleanup(self: Arc) { - if let Some(omikron_id) = self.state.read().await.omikron_id() { - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } + if self.id != 0 { + log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); + omikron_manager::remove_omikron(self.id as i64).await; + user_online_tracker::untrack_omikron(self.id as i64).await; } if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { @@ -1202,13 +1032,8 @@ impl OmikronConnection { } } - // Public API for external use - pub async fn is_authenticated(self: Arc) -> bool { - self.state.read().await.is_authenticated() - } - pub async fn get_omikron_id(self: Arc) -> Option { - self.state.read().await.omikron_id() + Some(self.id as i64) } pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { @@ -1220,27 +1045,60 @@ impl OmikronConnection { // Server Startup // ============================================================================ +pub async fn get_by_omikron_id( + omikron_id: u64, + _description: Option, +) -> Option { + sql::get_omikron_by_id(omikron_id as i64) + .await + .ok() + .map(|(bundle, _ip_address)| bundle) +} +pub async fn complete_register(pub_key: PublicKeyBundle, description: Option) -> u64 { + 0 +} + pub async fn start(port: u16) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: MTPHost = MTPHost::new( - HostConfig::new( - IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), + let host_config = HostConfig::new( + IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, cert_pem, key_pem, - ) ) - .await?; + .with_policy(Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(2_000), + write_timeout: Duration::from_millis(2_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + max_transient_recv_errors: 20, + transient_recv_backoff: Duration::from_millis(100), + receiver_queue_capacity: 1000, + }) + .with_authentication( + load_keyring(), + Box::new(|user_id, description| Box::pin(get_by_omikron_id(user_id, description))), + Box::new(|pub_key, description| Box::pin(complete_register(pub_key, description))), + ) + .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); + let mut host: Host = Host::new(host_config).await?; log!("OmikronServer listening on port {}", port); - while let Some((sender, mut receiver)) = host.next().await { + while let Ok(Some(mut connection)) = host.accept().await { tokio::spawn(async move { - let conn = OmikronConnection::new(sender); - conn.handle(&mut receiver).await; + let conn = OmikronConnection::new(connection.sender); + conn.handle(&mut connection.receiver).await; }); } From 3e7b5121d4972740f4964f48e4d5ffde137cc4a5 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 17:57:31 +0200 Subject: [PATCH 164/220] MTP migration --- Cargo.lock | 17 +- src/main.rs | 10 - src/notifications/mod.rs | 1 - src/notifications/tauri.rs | 157 --------------- src/server/api.rs | 12 +- src/sql/sql.rs | 63 +++--- src/transport/omikron_connection.rs | 288 +++++++--------------------- 7 files changed, 123 insertions(+), 425 deletions(-) delete mode 100644 src/notifications/mod.rs delete mode 100644 src/notifications/tauri.rs diff --git a/Cargo.lock b/Cargo.lock index 36da87b..592912f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "mtp-client", "mtp-codec", @@ -2294,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "base64", "byteorder", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "quinn", "rustls", @@ -2331,8 +2331,9 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ + "base64", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", @@ -2349,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "mtp-codec", "mtp-common", @@ -2362,7 +2363,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "log", "mtp-codec", @@ -2376,7 +2377,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#8dc8b54b26544554f98d7af32200d6bf9a01d300" +source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" dependencies = [ "serde", "serde_yaml", diff --git a/src/main.rs b/src/main.rs index c54fba3..8672167 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,8 @@ -mod notifications; mod server; mod sql; mod transport; mod util; -use crate::notifications::tauri; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::transport::omikron_connection; @@ -53,13 +51,6 @@ async fn main() { } }); - let tauri_handle = tokio::spawn(async move { - match tauri::start(9189).await { - Err(e) => log_err!(0, PrintType::General, "{:?}", e), - _ => {} - } - }); - log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { @@ -94,5 +85,4 @@ async fn main() { } omikron_handle.abort(); - tauri_handle.abort(); } diff --git a/src/notifications/mod.rs b/src/notifications/mod.rs deleted file mode 100644 index 62411f7..0000000 --- a/src/notifications/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod tauri; diff --git a/src/notifications/tauri.rs b/src/notifications/tauri.rs deleted file mode 100644 index 8195988..0000000 --- a/src/notifications/tauri.rs +++ /dev/null @@ -1,157 +0,0 @@ -use crate::log; -use crate::util::file_util::load_file_vec; -use crate::util::logger::PrintType; -use dashmap::DashMap; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; -use once_cell::sync::Lazy; -use std::net::{IpAddr, Ipv4Addr}; -use std::sync::Arc; -use std::time::Duration; - -pub struct TauriConnection { - pub user_id: i64, - pub sender: Arc, -} - -static TAURI_CONNECTIONS: Lazy>>> = Lazy::new(DashMap::new); - -pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - - let mut host: Host = host( - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - port, - cert_pem, - key_pem, - Policy { - send_mode: SendMode::SingleStreamPerMessage, - max_message_size: 1_000_000_000, - close_frame_len: u32::MAX, - application_close_code: 0, - open_stream_timeout: Duration::from_millis(2_000), - write_timeout: Duration::from_millis(2_000), - accept_stream_timeout: Duration::from_millis(10_000), - read_timeout: Duration::from_millis(30_000), - keep_alive_interval: Some(Duration::from_secs(6)), - max_idle_timeout: Some(Duration::from_secs(30)), - force_close_delay: Duration::from_millis(300), - max_transient_recv_errors: 20, - transient_recv_backoff: Duration::from_millis(100), - receiver_queue_capacity: 1000, - }, - ) - .await?; - - log!("TauriServer listening on port {}", port); - - while let Some((sender, mut receiver)) = host.next().await { - tokio::spawn(async move { - handle_connection(sender, &mut receiver).await; - }); - } - - Ok(()) -} - -async fn handle_connection(sender: Sender, receiver: &mut Receiver) { - let mut current_user_id = 0; - let sender = Arc::new(sender); - - while let Ok(cv) = receiver.receive().await { - if cv.is_type(CommunicationType::TauriIdentification) { - let user_id = cv.get_data(DataType::UserId).as_number().unwrap_or(0) as i64; - if user_id != 0 { - current_user_id = user_id; - let conn = Arc::new(TauriConnection { - user_id, - sender: sender.clone(), - }); - - TAURI_CONNECTIONS - .entry(user_id) - .and_modify(|conns| { - conns.retain(|c| !Arc::ptr_eq(&c.sender.handle(), &sender.handle())); - conns.push(conn.clone()); - }) - .or_insert_with(|| vec![conn]); - - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - if let Err(e) = sender.send(&response).await { - log!( - PrintType::General, - "Failed to send tauri success response: {}", - e - ); - } else { - log!(user_id, PrintType::Client, "Tauri device registered"); - } - } - } else if cv.is_type(CommunicationType::Ping) { - let response = - CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); - if let Err(_) = sender.send(&response).await { - break; - } - } - } - - // Cleanup - if current_user_id != 0 { - if let Some(mut conns) = TAURI_CONNECTIONS.get_mut(¤t_user_id) { - conns.retain(|c| !Arc::ptr_eq(c.sender.handle(), sender.handle())); - } - } -} - -pub async fn send_notification(user_id: i64, sender_id: i64) { - let conns_opt = { - let entry = TAURI_CONNECTIONS.get(&user_id); - entry.map(|e| e.clone()) - }; - - if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::PushNotification) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); - - let mut remove_needed = false; - for conn in conns.iter() { - if conn.sender.send(&cv).await.is_err() { - remove_needed = true; - } - } - - if remove_needed { - if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { - conns_mut.retain(|conn| conn.sender.is_open()); - } - } - } -} - -pub async fn remove_notification(user_id: i64, sender_id: i64) { - let conns_opt = { - let entry = TAURI_CONNECTIONS.get(&user_id); - entry.map(|e| e.clone()) - }; - - if let Some(conns) = conns_opt { - let cv = CommunicationValue::new(CommunicationType::ReadNotification) - .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender_id as i128)); - - let mut remove_needed = false; - for conn in conns.iter() { - if conn.sender.send(&cv).await.is_err() { - remove_needed = true; - } - } - - if remove_needed { - if let Some(mut conns_mut) = TAURI_CONNECTIONS.get_mut(&user_id) { - conns_mut.retain(|conn| conn.sender.is_open()); - } - } - } -} diff --git a/src/server/api.rs b/src/server/api.rs index 219d5fc..93c594e 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -68,7 +68,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) @@ -103,7 +103,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { @@ -111,7 +111,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) } else { @@ -126,7 +126,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); (StatusCode::OK, res.dump()) } else { @@ -172,7 +172,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["username"] = username.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["user_id"] = id.into(); res["iota_id"] = iota_id.into(); res["sub_level"] = sub_level.into(); @@ -227,7 +227,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["username"] = username.into(); - res["public_key"] = public_key.into(); + res["public_key"] = public_key.to_base64().into(); res["user_id"] = id.into(); res["iota_id"] = iota_id.into(); res["sub_level"] = sub_level.into(); diff --git a/src/sql/sql.rs b/src/sql/sql.rs index d9eb712..45b1899 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -1,4 +1,5 @@ use crate::log; +use mtp_crypto::PublicKeyBundle; use once_cell::sync::Lazy; use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -61,7 +62,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { avatar MEDIUMBLOB, sub_level INT(11) NOT NULL DEFAULT 0, sub_end BIGINT(20) NOT NULL DEFAULT 0, - public_key TEXT NOT NULL COLLATE utf8mb4_bin, + public_key BLOB NOT NULL, private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', iota_id BIGINT NOT NULL, token VARCHAR(256) NOT NULL UNIQUE COLLATE utf8mb4_bin @@ -73,7 +74,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS iotas ( id BIGINT NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + public_key BLOB NOT NULL )", ) .execute(&pool) @@ -82,7 +83,7 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { "CREATE TABLE IF NOT EXISTS omikrons ( id BIGINT NOT NULL PRIMARY KEY, - public_key VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + public_key BLOB NOT NULL, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin )", @@ -160,7 +161,7 @@ pub async fn get_by_username( Option>, i32, i64, - String, + PublicKeyBundle, String, String, ), @@ -192,7 +193,8 @@ pub async fn get_by_username( let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: Vec = row.get("public_key"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -206,7 +208,7 @@ pub async fn get_by_username( avatar, sub_level, sub_end, - String::from_utf8_lossy(&public_key).to_string(), + public_key, String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) @@ -228,7 +230,7 @@ pub async fn get_by_user_id( Option>, i32, i64, - String, + PublicKeyBundle, String, String, ), @@ -260,7 +262,8 @@ pub async fn get_by_user_id( let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: Vec = row.get("public_key"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -274,7 +277,7 @@ pub async fn get_by_user_id( avatar, sub_level, sub_end, - String::from_utf8_lossy(&public_key).to_string(), + public_key, String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )) @@ -296,7 +299,7 @@ pub async fn get_users_by_iota_id( Option>, i32, i64, - String, + PublicKeyBundle, String, String, )>, @@ -328,7 +331,8 @@ pub async fn get_users_by_iota_id( let avatar: Option> = row.get("avatar"); let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); - let public_key: Vec = row.get("public_key"); + let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -342,7 +346,7 @@ pub async fn get_users_by_iota_id( avatar, sub_level, sub_end, - String::from_utf8_lossy(&public_key).to_string(), + public_key, String::from_utf8_lossy(&private_key_hash).to_string(), String::from_utf8_lossy(&token).to_string(), )); @@ -477,7 +481,7 @@ pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error pub async fn change_keys( id: i64, - new_public_key: String, + new_public_key: PublicKeyBundle, new_private_key_hash: String, ) -> Result<(), sqlx::Error> { let pool = { @@ -489,7 +493,7 @@ pub async fn change_keys( }; sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key.as_bytes().to_vec()) + .bind(new_public_key.as_bytes()) .bind(new_private_key_hash.as_bytes().to_vec()) .bind(id) .execute(&pool) @@ -517,7 +521,7 @@ pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> pub async fn register_complete_user( id: i64, username: String, - public_key: String, + public_key: PublicKeyBundle, iota_id: i64, token: String, ) -> Result<(), sqlx::Error> { @@ -534,7 +538,7 @@ pub async fn register_complete_user( ) .bind(id) .bind(username.as_bytes().to_vec()) - .bind(public_key.as_bytes().to_vec()) + .bind(public_key.as_bytes()) .bind(iota_id) .bind(token.as_bytes().to_vec()) .execute(&pool) @@ -589,13 +593,13 @@ pub async fn print_users() -> Result<(), Box> { // ========================================================================================== // IOTA // ========================================================================================== -pub async fn create_new_iota(public_key: String) -> Result { +pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { let new_id = get_register_id().await as i64; register_complete_iota(new_id, public_key).await?; Ok(new_id) } -pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), sqlx::Error> { +pub async fn register_complete_iota(id: i64, public_key: PublicKeyBundle) -> Result<(), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -606,14 +610,14 @@ pub async fn register_complete_iota(id: i64, public_key: String) -> Result<(), s sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id) - .bind(public_key.as_bytes().to_vec()) + .bind(public_key.as_bytes()) .execute(&pool) .await?; Ok(()) } -pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { +pub async fn get_iota_by_id(id: i64) -> Result<(i64, PublicKeyBundle), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -631,7 +635,9 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { match result { Ok(optional_row) => match optional_row { Some((id_i64, public_key)) => { - Ok((id_i64, String::from_utf8_lossy(&public_key).to_string())) + let bundle = PublicKeyBundle::from_bytes(&public_key) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + Ok((id_i64, bundle)) } _ => Err(sqlx::Error::RowNotFound), }, @@ -639,7 +645,7 @@ pub async fn get_iota_by_id(id: i64) -> Result<(i64, String), sqlx::Error> { } } -pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error> { +pub async fn change_iota_key(id: i64, new_key: PublicKeyBundle) -> Result<(), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -649,7 +655,7 @@ pub async fn change_iota_key(id: i64, new_key: String) -> Result<(), sqlx::Error }; sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(new_key.as_bytes().to_vec()) + .bind(new_key.as_bytes()) .bind(id) .execute(&pool) .await?; @@ -678,7 +684,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { // OMIKRONS // ========================================================================================== -pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> { +pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -695,10 +701,11 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(String, String), sqlx::Error> .await?; match row { - Some((public_key, ip_address)) => Ok(( - String::from_utf8_lossy(&public_key).to_string(), - String::from_utf8_lossy(&ip_address).to_string(), - )), + Some((public_key, ip_address)) => { + let bundle = PublicKeyBundle::from_bytes(&public_key) + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + Ok((bundle, String::from_utf8_lossy(&ip_address).to_string())) + } _ => Err(sqlx::Error::RowNotFound), } } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 982db94..cb11548 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,9 +1,9 @@ use crate::{ - get_keyring, get_public_key_bundle, log, log_cv_in, log_cv_out, log_err, log_in, + load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server::short_link::add_short_link, sql::{ connection_status::UserStatus, - sql::{self, get_by_user_id, get_by_username, get_iota_by_id, get_omikron_by_id}, + sql::{self, get_by_user_id, get_by_username, get_iota_by_id}, user_online_tracker::{self}, }, transport::omikron_manager, @@ -11,9 +11,12 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, host::{HostConfig, MTPHost}}; -use mtp::transport::{Host, Policy, Receiver, SendMode, Sender, host}; -use rand::{Rng, distributions::Alphanumeric}; +use mtp::host::{AuthenticationPolicy, Receiver, Sender}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + host::{Host, HostConfig, Policy, SendMode}, +}; +use mtp_crypto::PublicKeyBundle; use std::net::{IpAddr, Ipv4Addr}; use std::{ sync::Arc, @@ -23,8 +26,6 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; -use x448::PublicKey; - // ============================================================================ // Configuration // ============================================================================ @@ -63,35 +64,6 @@ pub struct WaitingTask { pub inserted_at: Instant, } -// ============================================================================ -// Connection State -// ============================================================================ - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum AuthState { - Unauthenticated, - Identified { omikron_id: i64 }, - Authenticated { omikron_id: i64 }, -} - -impl AuthState { - fn is_authenticated(&self) -> bool { - match self { - AuthState::Authenticated { omikron_id } => true, - _ => false, - } - } - - fn omikron_id(&self) -> Option { - match self { - AuthState::Identified { omikron_id } | AuthState::Authenticated { omikron_id } => { - Some(*omikron_id) - } - _ => None, - } - } -} - // ============================================================================ // Omikron Connection (mtp/QUIC-based) // ============================================================================ @@ -99,7 +71,6 @@ impl AuthState { pub struct OmikronConnection { id: u64, sender: Mutex>, - state: RwLock, challenge: RwLock, pub_key: RwLock>>, pub ping: RwLock, @@ -124,7 +95,6 @@ impl OmikronConnection { let conn = Arc::new(Self { id: rand::random(), sender: Mutex::new(Some(sender)), - state: RwLock::new(AuthState::Unauthenticated), challenge: RwLock::new(String::new()), pub_key: RwLock::new(None), ping: RwLock::new(-1), @@ -197,144 +167,9 @@ impl OmikronConnection { if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } - - let current_state = *self.state.read().await; - // Route based on authentication state - match current_state { - AuthState::Unauthenticated => self.clone().handle_unauthenticated(cv).await, - AuthState::Identified { .. } => self.clone().handle_identified(cv).await, - AuthState::Authenticated { omikron_id } => { - self.clone().handle_authenticated(cv, omikron_id).await - } - } + return Ok(()); } - // ------------------------------------------------------------------------- - // Authentication Handlers - // ------------------------------------------------------------------------- - - async fn handle_unauthenticated(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::Identification) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - // Extract omikron ID - let omikron_id = cv - .get_data(DataType::OmikronId) - .as_number() - .ok_or(OmikronError::InvalidResponse)?; - log!("Omikron {:?} connected", omikron_id); - - // Lookup omikron in database - let (public_key, _) = get_omikron_by_id(omikron_id as i64) - .await - .map_err(|e| OmikronError::Sql(e.to_string()))?; - - log!("Got public Key"); - - let pub_key_bytes = STANDARD - .decode(&public_key) - .map_err(|_| OmikronError::AuthenticationFailed)?; - - let pub_key_bytes_clone = pub_key_bytes.clone(); - let omikron_pub_key = PublicKey::from_bytes(&pub_key_bytes_clone) - .ok_or(OmikronError::AuthenticationFailed)?; - - log!("Decoded public Key"); - - // Generate challenge - let challenge: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(32) - .map(char::from) - .collect(); - - log!("Generated Challenge"); - - *self.challenge.write().await = challenge.clone(); - - log!("Stored Challenge"); - - *self.pub_key.write().await = Some(pub_key_bytes); - - log!("Stored Pubkey"); - - *self.state.write().await = AuthState::Identified { - omikron_id: omikron_id as i64, - }; - - log!("Stored State"); - - let challenge_clone: String = challenge.clone(); - let private_key = get_keyring(); - let public_key_for_encrypt = omikron_pub_key; - - let encrypted = tokio::task::spawn_blocking(move || { - challenge_clone - encrypt(private_key, public_key_for_encrypt, &challenge_clone) - .map_err(|_| OmikronError::AuthenticationFailed) - }) - .await - .map_err(|_| OmikronError::AuthenticationFailed)??; - - log!("Encrypted Challenge"); - - // Send challenge response - let response = CommunicationValue::new(CommunicationType::Challenge) - .with_id(cv.get_id()) - .add_typed_default( - DataType::PublicKey, - DataValue::Str(STANDARD.encode(get_public_key_bundle().as_bytes())), - ) - .add_typed_default(DataType::Content, DataValue::Str(encrypted)); - - log!("Sending Challenge"); - self.send(&response).await - } - - async fn handle_identified(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::ChallengeResponse) { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::ErrorNotAuthenticated) - .await; - return Err(OmikronError::NotAuthenticated); - } - - let client_response = cv - .get_data(DataType::Content) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let expected_challenge = self.challenge.read().await.clone(); - - if client_response == expected_challenge { - let omikron_id = self.state.read().await.omikron_id().unwrap_or(0); - *self.state.write().await = AuthState::Authenticated { omikron_id }; - - omikron_manager::add_omikron(self.clone()).await; - - let response = CommunicationValue::new(CommunicationType::IdentificationResponse) - .with_id(cv.get_id()) - .add_typed_default(DataType::Accepted, DataValue::Bool(true)); - - self.clone().send(&response).await?; - log_in!(omikron_id, PrintType::Omega, "Omikron authenticated"); - Ok(()) - } else { - let _ = self - .send_error_response(cv.get_id(), CommunicationType::ErrorInvalidChallenge) - .await; - Err(OmikronError::AuthenticationFailed) - } - } - - // ------------------------------------------------------------------------- - // Authenticated Message Handlers - // ------------------------------------------------------------------------- - async fn handle_authenticated( self: Arc, cv: CommunicationValue, @@ -554,7 +389,7 @@ impl OmikronConnection { Option>, i32, i64, - String, + PublicKeyBundle, String, String, ), @@ -577,7 +412,7 @@ impl OmikronConnection { let mut response = CommunicationValue::new(CommunicationType::GetUserData) .with_id(msg_id) .add_typed_default(DataType::Username, DataValue::Str(username.clone())) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default( @@ -704,13 +539,13 @@ impl OmikronConnection { self: Arc, msg_id: u32, iota_id: i64, - public_key: String, + public_key: PublicKeyBundle, user_id: Option, username: Option, ) -> CommunicationValue { let mut response = CommunicationValue::new(CommunicationType::GetIotaData) .with_id(msg_id) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key)) + .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); if let Some(uid) = user_id { @@ -752,10 +587,15 @@ impl OmikronConnection { ) -> OmikronResult<()> { let iota_id_opt = cv.get_data(DataType::IotaId).as_number().map(|n| n as i64); - if let Some(public_key) = cv.get_data(DataType::PublicKey).as_str() { + let public_key = cv + .get_data(DataType::PublicKey) + .as_str() + .and_then(|s| PublicKeyBundle::from_base64(s).ok()); + + if let Some(public_key) = public_key { if let Some(iota_id) = iota_id_opt { // Register existing IOTA - match sql::register_complete_iota(iota_id, public_key.to_string()).await { + match sql::register_complete_iota(iota_id, public_key).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::Success) .with_id(cv.get_id()); @@ -770,7 +610,7 @@ impl OmikronConnection { } } else { // Create new IOTA - match sql::create_new_iota(public_key.to_string()).await { + match sql::create_new_iota(public_key).await { Ok(new_iota_id) => { let response = CommunicationValue::new(CommunicationType::CompleteRegisterIota) @@ -807,7 +647,7 @@ impl OmikronConnection { let public_key = cv .get_data(DataType::PublicKey) .as_str() - .map(|s| s.to_string()); + .and_then(|s| PublicKeyBundle::from_base64(s).ok()); let iota_id = cv.get_sender(); let reset_token = cv .get_data(DataType::ResetToken) @@ -873,15 +713,13 @@ impl OmikronConnection { } } if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataType::PublicKey).as_str(), + cv.get_data(DataType::PublicKey) + .as_str() + .and_then(|s| PublicKeyBundle::from_base64(s).ok()), cv.get_data(DataType::PrivateKeyHash).as_str(), ) { - if let Err(e) = sql::change_keys( - user_id, - public_key.to_string(), - private_key_hash.to_string(), - ) - .await + if let Err(e) = + sql::change_keys(user_id, public_key, private_key_hash.to_string()).await { success = false; error_message = e.to_string(); @@ -1051,9 +889,6 @@ impl OmikronConnection { .with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with Tauri - crate::notifications::tauri::remove_notification(receiver_id, other_id).await; - // Sync with other Omikron clients let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) @@ -1091,9 +926,6 @@ impl OmikronConnection { CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with Tauri - crate::notifications::tauri::send_notification(receiver_id, sender_id).await; - // Sync with other Omikron clients let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) @@ -1189,12 +1021,10 @@ impl OmikronConnection { } async fn cleanup(self: Arc) { - if let Some(omikron_id) = self.state.read().await.omikron_id() { - if omikron_id != 0 { - log_in!(omikron_id, PrintType::Omega, "Omikron disconnected"); - omikron_manager::remove_omikron(omikron_id).await; - user_online_tracker::untrack_omikron(omikron_id).await; - } + if self.id != 0 { + log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); + omikron_manager::remove_omikron(self.id as i64).await; + user_online_tracker::untrack_omikron(self.id as i64).await; } if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { @@ -1202,13 +1032,8 @@ impl OmikronConnection { } } - // Public API for external use - pub async fn is_authenticated(self: Arc) -> bool { - self.state.read().await.is_authenticated() - } - pub async fn get_omikron_id(self: Arc) -> Option { - self.state.read().await.omikron_id() + Some(self.id as i64) } pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { @@ -1220,27 +1045,60 @@ impl OmikronConnection { // Server Startup // ============================================================================ +pub async fn get_by_omikron_id( + omikron_id: u64, + _description: Option, +) -> Option { + sql::get_omikron_by_id(omikron_id as i64) + .await + .ok() + .map(|(bundle, _ip_address)| bundle) +} +pub async fn complete_register(pub_key: PublicKeyBundle, description: Option) -> u64 { + 0 +} + pub async fn start(port: u16) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - let mut host: MTPHost = MTPHost::new( - HostConfig::new( - IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), + let host_config = HostConfig::new( + IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, cert_pem, key_pem, - ) ) - .await?; + .with_policy(Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(2_000), + write_timeout: Duration::from_millis(2_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + max_transient_recv_errors: 20, + transient_recv_backoff: Duration::from_millis(100), + receiver_queue_capacity: 1000, + }) + .with_authentication( + load_keyring(), + Box::new(|user_id, description| Box::pin(get_by_omikron_id(user_id, description))), + Box::new(|pub_key, description| Box::pin(complete_register(pub_key, description))), + ) + .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); + let mut host: Host = Host::new(host_config).await?; log!("OmikronServer listening on port {}", port); - while let Some((sender, mut receiver)) = host.next().await { + while let Ok(Some(mut connection)) = host.accept().await { tokio::spawn(async move { - let conn = OmikronConnection::new(sender); - conn.handle(&mut receiver).await; + let conn = OmikronConnection::new(connection.sender); + conn.handle(&mut connection.receiver).await; }); } From fb71283802e5d33ac683c6c35c2401375ca5d7cb Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:17:21 +0200 Subject: [PATCH 165/220] [WIP] MTP migration --- .gitignore | 3 +++ Cargo.lock | 26 ++++++++++++++++++-------- Cargo.toml | 7 +++++-- src/main.rs | 29 +++++++++++++++++++---------- src/server/api.rs | 4 ++-- src/transport/omikron_connection.rs | 20 ++++++++++---------- 6 files changed, 57 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index e3d273c..176ffc8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ target # Added by cargo /target + +*.mk +*.mpkb diff --git a/Cargo.lock b/Cargo.lock index 592912f..16c9bf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2280,12 +2280,13 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "mtp-client", "mtp-codec", "mtp-common", "mtp-crypto", + "mtp-files", "mtp-host", "mtp-transport", "mtp-type-map", @@ -2294,7 +2295,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2308,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "base64", "byteorder", @@ -2320,7 +2321,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "quinn", "rustls", @@ -2331,7 +2332,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "base64", "chacha20poly1305", @@ -2347,10 +2348,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mtp-files" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +dependencies = [ + "mtp-crypto", + "thiserror 1.0.69", +] + [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "mtp-codec", "mtp-common", @@ -2363,7 +2373,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "log", "mtp-codec", @@ -2377,7 +2387,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "serde", "serde_yaml", diff --git a/Cargo.toml b/Cargo.toml index e9a78f4..d3de686 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ "host", - "crypto" + "crypto", + "files", ] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } @@ -39,4 +40,6 @@ uuid = { version = "1.19.0", features = ["v4"] } x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" -mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = ["pqc"] } +mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = [ + "pqc", +] } diff --git a/src/main.rs b/src/main.rs index 8672167..2abf3a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,23 +9,32 @@ use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; -use base64::Engine as _; use dotenv::from_path; -use mtp_crypto::{Keyring, PublicKeyBundle}; +use mtp::files::{load_keyring as load_keyring_file, save_keyring, save_public_key_bundle}; +use mtp_crypto::Keyring; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; -static KEYRING_ENV: Lazy = Lazy::new(|| env::var("KEYRING").unwrap()); -fn load_keyring() -> Keyring { - let bytes = base64::engine::general_purpose::STANDARD - .decode(&*KEYRING_ENV) - .expect("Invalid KEYRING env var: not valid base64"); - Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize") +const KEYRING_PATH: &str = "./omega.mk"; + +static KEYRING: Lazy = Lazy::new(|| { + load_keyring_file(KEYRING_PATH).unwrap_or_else(|_| { + let kr = Keyring::generate(); + save_keyring(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); + save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) + .expect("Failed to save generated public key bundle"); + eprintln!("Generated new keyring at {}", KEYRING_PATH); + kr + }) +}); + +pub fn get_keyring() -> &'static Keyring { + &KEYRING } -pub fn get_public_key_bundle() -> PublicKeyBundle { - load_keyring().public_key_bundle() +pub fn load_keyring() -> Keyring { + Keyring::from_bytes(&KEYRING.to_bytes()).unwrap() } #[tokio::main] diff --git a/src/server/api.rs b/src/server/api.rs index 93c594e..ae251cf 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,4 +1,4 @@ -use crate::get_public_key_bundle; +use crate::load_keyring; use crate::sql::sql; use crate::sql::sql::{get_by_user_id, get_omikron_by_id}; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; @@ -192,7 +192,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { ["api", "get", "public_key"] => { let mut res = JsonValue::new_object(); res["status"] = "success".into(); - let bundle = get_public_key_bundle(); + let bundle = load_keyring().public_key_bundle(); res["public_key"] = base64::engine::general_purpose::STANDARD .encode(bundle.as_bytes()) .into(); diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index cb11548..4435fdb 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -71,8 +71,6 @@ pub struct WaitingTask { pub struct OmikronConnection { id: u64, sender: Mutex>, - challenge: RwLock, - pub_key: RwLock>>, pub ping: RwLock, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, @@ -91,12 +89,10 @@ impl OmikronConnection { // Construction // ------------------------------------------------------------------------- - pub fn new(sender: Sender) -> Arc { + pub fn new(sender: Sender, id: u64) -> Arc { let conn = Arc::new(Self { - id: rand::random(), + id, sender: Mutex::new(Some(sender)), - challenge: RwLock::new(String::new()), - pub_key: RwLock::new(None), ping: RwLock::new(-1), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), @@ -163,11 +159,14 @@ impl OmikronConnection { return Ok(()); } - // Handle ping regardless of auth state + // Handle ping regardless of message type if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } - return Ok(()); + + // Authentication is completed by the mtp host before this connection exists. + let omikron_id = self.id as i64; + self.clone().handle_authenticated(cv, omikron_id).await } async fn handle_authenticated( @@ -1054,7 +1053,7 @@ pub async fn get_by_omikron_id( .ok() .map(|(bundle, _ip_address)| bundle) } -pub async fn complete_register(pub_key: PublicKeyBundle, description: Option) -> u64 { +pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option) -> u64 { 0 } @@ -1097,7 +1096,8 @@ pub async fn start(port: u16) -> Result<(), Box> { while let Ok(Some(mut connection)) = host.accept().await { tokio::spawn(async move { - let conn = OmikronConnection::new(connection.sender); + let conn = OmikronConnection::new(connection.sender, connection.client_id); + omikron_manager::add_omikron(conn.clone()).await; conn.handle(&mut connection.receiver).await; }); } From 69e6b8b70e22e2dcc63e09257def66d710807f7a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:17:21 +0200 Subject: [PATCH 166/220] [WIP] MTP migration --- .gitignore | 3 +++ Cargo.lock | 26 ++++++++++++++++++-------- Cargo.toml | 7 +++++-- src/main.rs | 29 +++++++++++++++++++---------- src/server/api.rs | 4 ++-- src/transport/omikron_connection.rs | 20 ++++++++++---------- 6 files changed, 57 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index e3d273c..176ffc8 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ target # Added by cargo /target + +*.mk +*.mpkb diff --git a/Cargo.lock b/Cargo.lock index 592912f..16c9bf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2280,12 +2280,13 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "mtp-client", "mtp-codec", "mtp-common", "mtp-crypto", + "mtp-files", "mtp-host", "mtp-transport", "mtp-type-map", @@ -2294,7 +2295,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2308,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "base64", "byteorder", @@ -2320,7 +2321,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "quinn", "rustls", @@ -2331,7 +2332,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "base64", "chacha20poly1305", @@ -2347,10 +2348,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mtp-files" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +dependencies = [ + "mtp-crypto", + "thiserror 1.0.69", +] + [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "mtp-codec", "mtp-common", @@ -2363,7 +2373,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "log", "mtp-codec", @@ -2377,7 +2387,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#44ff1d8781b4645dbff3dce2406155e4d9d43a4c" +source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" dependencies = [ "serde", "serde_yaml", diff --git a/Cargo.toml b/Cargo.toml index e9a78f4..d3de686 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,8 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ "host", - "crypto" + "crypto", + "files", ] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } @@ -39,4 +40,6 @@ uuid = { version = "1.19.0", features = ["v4"] } x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" -mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = ["pqc"] } +mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = [ + "pqc", +] } diff --git a/src/main.rs b/src/main.rs index 8672167..2abf3a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,23 +9,32 @@ use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; -use base64::Engine as _; use dotenv::from_path; -use mtp_crypto::{Keyring, PublicKeyBundle}; +use mtp::files::{load_keyring as load_keyring_file, save_keyring, save_public_key_bundle}; +use mtp_crypto::Keyring; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; -static KEYRING_ENV: Lazy = Lazy::new(|| env::var("KEYRING").unwrap()); -fn load_keyring() -> Keyring { - let bytes = base64::engine::general_purpose::STANDARD - .decode(&*KEYRING_ENV) - .expect("Invalid KEYRING env var: not valid base64"); - Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize") +const KEYRING_PATH: &str = "./omega.mk"; + +static KEYRING: Lazy = Lazy::new(|| { + load_keyring_file(KEYRING_PATH).unwrap_or_else(|_| { + let kr = Keyring::generate(); + save_keyring(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); + save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) + .expect("Failed to save generated public key bundle"); + eprintln!("Generated new keyring at {}", KEYRING_PATH); + kr + }) +}); + +pub fn get_keyring() -> &'static Keyring { + &KEYRING } -pub fn get_public_key_bundle() -> PublicKeyBundle { - load_keyring().public_key_bundle() +pub fn load_keyring() -> Keyring { + Keyring::from_bytes(&KEYRING.to_bytes()).unwrap() } #[tokio::main] diff --git a/src/server/api.rs b/src/server/api.rs index 93c594e..ae251cf 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,4 +1,4 @@ -use crate::get_public_key_bundle; +use crate::load_keyring; use crate::sql::sql; use crate::sql::sql::{get_by_user_id, get_omikron_by_id}; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; @@ -192,7 +192,7 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { ["api", "get", "public_key"] => { let mut res = JsonValue::new_object(); res["status"] = "success".into(); - let bundle = get_public_key_bundle(); + let bundle = load_keyring().public_key_bundle(); res["public_key"] = base64::engine::general_purpose::STANDARD .encode(bundle.as_bytes()) .into(); diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index cb11548..4435fdb 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -71,8 +71,6 @@ pub struct WaitingTask { pub struct OmikronConnection { id: u64, sender: Mutex>, - challenge: RwLock, - pub_key: RwLock>>, pub ping: RwLock, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, @@ -91,12 +89,10 @@ impl OmikronConnection { // Construction // ------------------------------------------------------------------------- - pub fn new(sender: Sender) -> Arc { + pub fn new(sender: Sender, id: u64) -> Arc { let conn = Arc::new(Self { - id: rand::random(), + id, sender: Mutex::new(Some(sender)), - challenge: RwLock::new(String::new()), - pub_key: RwLock::new(None), ping: RwLock::new(-1), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), @@ -163,11 +159,14 @@ impl OmikronConnection { return Ok(()); } - // Handle ping regardless of auth state + // Handle ping regardless of message type if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } - return Ok(()); + + // Authentication is completed by the mtp host before this connection exists. + let omikron_id = self.id as i64; + self.clone().handle_authenticated(cv, omikron_id).await } async fn handle_authenticated( @@ -1054,7 +1053,7 @@ pub async fn get_by_omikron_id( .ok() .map(|(bundle, _ip_address)| bundle) } -pub async fn complete_register(pub_key: PublicKeyBundle, description: Option) -> u64 { +pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option) -> u64 { 0 } @@ -1097,7 +1096,8 @@ pub async fn start(port: u16) -> Result<(), Box> { while let Ok(Some(mut connection)) = host.accept().await { tokio::spawn(async move { - let conn = OmikronConnection::new(connection.sender); + let conn = OmikronConnection::new(connection.sender, connection.client_id); + omikron_manager::add_omikron(conn.clone()).await; conn.handle(&mut connection.receiver).await; }); } From fb4facb8c477355d4e81811a41efa1cc2e24e002 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:41:01 +0200 Subject: [PATCH 167/220] [COMPLETE] MTP-MIGRATION [Some errors to be found] --- src/server/api.rs | 38 +++++++++++++++++++++++++---- src/server/server.rs | 2 +- src/sql/sql.rs | 24 +++++++++++++----- src/transport/omikron_connection.rs | 22 +++++++++++++---- 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/server/api.rs b/src/server/api.rs index ae251cf..faf11b7 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,6 +1,6 @@ use crate::load_keyring; use crate::sql::sql; -use crate::sql::sql::{get_by_user_id, get_omikron_by_id}; +use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; @@ -64,12 +64,13 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { if let Some(id) = omikron_conn.get_omikron_id().await { - if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + if let Ok((public_key, ip_address, port)) = sql::get_omikron_by_id(id).await { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else { @@ -99,20 +100,22 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "error_bad_request".into(); (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { + } else if let Ok((public_key, ip_address, port)) = get_omikron_by_id(id).await { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else { let mut res = JsonValue::new_object(); @@ -122,12 +125,14 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await { if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await + { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else { let mut res = JsonValue::new_object(); @@ -146,6 +151,29 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } } + // ================================================== + // GET IOTA BY ID + // ================================================== + ["api", "get", "iota", id] => { + let id: i64 = id.parse().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok((id, public_key)) = get_iota_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["iota_id"] = id.into(); + res["public_key"] = public_key.to_base64().into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + // ================================================== // GET ID BY USERNAME // ================================================== diff --git a/src/server/server.rs b/src/server/server.rs index c367679..3c7fc05 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -28,7 +28,7 @@ pub async fn start(port: u16) -> anyhow::Result<()> { .with_no_client_auth() .with_single_cert(cert_chain, key)?; - config.alpn_protocols = vec![b"h2".to_vec(), b"hmtp/1.1".to_vec()]; + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); let addr = format!("{}:{}", bind_addr, port); diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 45b1899..da6d1bc 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -85,11 +85,19 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { id BIGINT NOT NULL PRIMARY KEY, public_key BLOB NOT NULL, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + port INT(11) NOT NULL DEFAULT 959 )", ) .execute(&pool) .await; + // Retrofits `port` onto omikrons tables created before this column existed; + // `CREATE TABLE IF NOT EXISTS` above is a no-op against an already-existing table. + let _ = sqlx::query( + "ALTER TABLE omikrons ADD COLUMN IF NOT EXISTS port INT(11) NOT NULL DEFAULT 959", + ) + .execute(&pool) + .await; let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS notifications ( @@ -684,7 +692,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { // OMIKRONS // ========================================================================================== -pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String), sqlx::Error> { +pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String, u16), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -693,18 +701,22 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String), sql .expect("Database pool not initialized") }; - let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = ?", + let row = sqlx::query_as::<_, (Vec, Vec, i32)>( + "SELECT public_key, ip_address, port FROM omikrons WHERE id = ?", ) .bind(id) .fetch_optional(&pool) .await?; match row { - Some((public_key, ip_address)) => { + Some((public_key, ip_address, port)) => { let bundle = PublicKeyBundle::from_bytes(&public_key) .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - Ok((bundle, String::from_utf8_lossy(&ip_address).to_string())) + Ok(( + bundle, + String::from_utf8_lossy(&ip_address).to_string(), + port as u16, + )) } _ => Err(sqlx::Error::RowNotFound), } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 4435fdb..2b2ba69 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1051,7 +1051,7 @@ pub async fn get_by_omikron_id( sql::get_omikron_by_id(omikron_id as i64) .await .ok() - .map(|(bundle, _ip_address)| bundle) + .map(|(bundle, _ip_address, _port)| bundle) } pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option) -> u64 { 0 @@ -1094,11 +1094,23 @@ pub async fn start(port: u16) -> Result<(), Box> { let mut host: Host = Host::new(host_config).await?; log!("OmikronServer listening on port {}", port); - while let Ok(Some(mut connection)) = host.accept().await { + loop { + let mut conn = match host.accept().await { + Ok(Some(conn)) => conn, + Ok(None) => break, + Err(e) => { + // A single omikron's failed/aborted handshake (bad auth, a + // probe, a mid-handshake disconnect) must not take down the + // whole listener - only that connection attempt is lost. + log_err!(0, PrintType::Omega, "Rejected omikron connection: {}", e); + continue; + } + }; + + let omikron_connection = OmikronConnection::new(conn.sender, conn.client_id); tokio::spawn(async move { - let conn = OmikronConnection::new(connection.sender, connection.client_id); - omikron_manager::add_omikron(conn.clone()).await; - conn.handle(&mut connection.receiver).await; + omikron_manager::add_omikron(omikron_connection.clone()).await; + omikron_connection.handle(&mut conn.receiver).await; }); } From 065d8245e28328909fbf4f24a0a8895d84ff6968 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 4 Jul 2026 02:41:01 +0200 Subject: [PATCH 168/220] [COMPLETE] MTP-MIGRATION [Some errors to be found] --- src/server/api.rs | 38 +++++++++++++++++++++++++---- src/server/server.rs | 2 +- src/sql/sql.rs | 24 +++++++++++++----- src/transport/omikron_connection.rs | 22 +++++++++++++---- 4 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/server/api.rs b/src/server/api.rs index ae251cf..faf11b7 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,6 +1,6 @@ use crate::load_keyring; use crate::sql::sql; -use crate::sql::sql::{get_by_user_id, get_omikron_by_id}; +use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; @@ -64,12 +64,13 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { ["api", "get", "omikron"] => { if let Ok(omikron_conn) = get_random_omikron().await { if let Some(id) = omikron_conn.get_omikron_id().await { - if let Ok((public_key, ip_address)) = sql::get_omikron_by_id(id).await { + if let Ok((public_key, ip_address, port)) = sql::get_omikron_by_id(id).await { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else { @@ -99,20 +100,22 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { let mut res = JsonValue::new_object(); res["status"] = "error_bad_request".into(); (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((public_key, ip_address)) = get_omikron_by_id(id).await { + } else if let Ok((public_key, ip_address, port)) = get_omikron_by_id(id).await { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else { let mut res = JsonValue::new_object(); @@ -122,12 +125,14 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await { if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address)) = get_omikron_by_id(omikron_id).await { + if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await + { let mut res = JsonValue::new_object(); res["status"] = "success".into(); res["id"] = omikron_id.into(); res["public_key"] = public_key.to_base64().into(); res["ip_address"] = ip_address.into(); + res["port"] = port.into(); (StatusCode::OK, res.dump()) } else { let mut res = JsonValue::new_object(); @@ -146,6 +151,29 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } } + // ================================================== + // GET IOTA BY ID + // ================================================== + ["api", "get", "iota", id] => { + let id: i64 = id.parse().unwrap_or(0); + + if id == 0 { + let mut res = JsonValue::new_object(); + res["status"] = "error_bad_request".into(); + (StatusCode::BAD_REQUEST, res.dump()) + } else if let Ok((id, public_key)) = get_iota_by_id(id).await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + res["iota_id"] = id.into(); + res["public_key"] = public_key.to_base64().into(); + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error_not_found".into(); + (StatusCode::NOT_FOUND, res.dump()) + } + } + // ================================================== // GET ID BY USERNAME // ================================================== diff --git a/src/server/server.rs b/src/server/server.rs index c367679..3c7fc05 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -28,7 +28,7 @@ pub async fn start(port: u16) -> anyhow::Result<()> { .with_no_client_auth() .with_single_cert(cert_chain, key)?; - config.alpn_protocols = vec![b"h2".to_vec(), b"hmtp/1.1".to_vec()]; + config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); let addr = format!("{}:{}", bind_addr, port); diff --git a/src/sql/sql.rs b/src/sql/sql.rs index 45b1899..da6d1bc 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -85,11 +85,19 @@ pub async fn initialize_db() -> Result<(), sqlx::Error> { id BIGINT NOT NULL PRIMARY KEY, public_key BLOB NOT NULL, location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin + ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, + port INT(11) NOT NULL DEFAULT 959 )", ) .execute(&pool) .await; + // Retrofits `port` onto omikrons tables created before this column existed; + // `CREATE TABLE IF NOT EXISTS` above is a no-op against an already-existing table. + let _ = sqlx::query( + "ALTER TABLE omikrons ADD COLUMN IF NOT EXISTS port INT(11) NOT NULL DEFAULT 959", + ) + .execute(&pool) + .await; let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS notifications ( @@ -684,7 +692,7 @@ pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { // OMIKRONS // ========================================================================================== -pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String), sqlx::Error> { +pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String, u16), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock @@ -693,18 +701,22 @@ pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String), sql .expect("Database pool not initialized") }; - let row = sqlx::query_as::<_, (Vec, Vec)>( - "SELECT public_key, ip_address FROM omikrons WHERE id = ?", + let row = sqlx::query_as::<_, (Vec, Vec, i32)>( + "SELECT public_key, ip_address, port FROM omikrons WHERE id = ?", ) .bind(id) .fetch_optional(&pool) .await?; match row { - Some((public_key, ip_address)) => { + Some((public_key, ip_address, port)) => { let bundle = PublicKeyBundle::from_bytes(&public_key) .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - Ok((bundle, String::from_utf8_lossy(&ip_address).to_string())) + Ok(( + bundle, + String::from_utf8_lossy(&ip_address).to_string(), + port as u16, + )) } _ => Err(sqlx::Error::RowNotFound), } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 4435fdb..2b2ba69 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1051,7 +1051,7 @@ pub async fn get_by_omikron_id( sql::get_omikron_by_id(omikron_id as i64) .await .ok() - .map(|(bundle, _ip_address)| bundle) + .map(|(bundle, _ip_address, _port)| bundle) } pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option) -> u64 { 0 @@ -1094,11 +1094,23 @@ pub async fn start(port: u16) -> Result<(), Box> { let mut host: Host = Host::new(host_config).await?; log!("OmikronServer listening on port {}", port); - while let Ok(Some(mut connection)) = host.accept().await { + loop { + let mut conn = match host.accept().await { + Ok(Some(conn)) => conn, + Ok(None) => break, + Err(e) => { + // A single omikron's failed/aborted handshake (bad auth, a + // probe, a mid-handshake disconnect) must not take down the + // whole listener - only that connection attempt is lost. + log_err!(0, PrintType::Omega, "Rejected omikron connection: {}", e); + continue; + } + }; + + let omikron_connection = OmikronConnection::new(conn.sender, conn.client_id); tokio::spawn(async move { - let conn = OmikronConnection::new(connection.sender, connection.client_id); - omikron_manager::add_omikron(conn.clone()).await; - conn.handle(&mut connection.receiver).await; + omikron_manager::add_omikron(omikron_connection.clone()).await; + omikron_connection.handle(&mut conn.receiver).await; }); } From 7a457e97d8f3229852371a12e9d74c030923528b Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 4 Jul 2026 23:02:04 +0200 Subject: [PATCH 169/220] [Fix]Connection Stability --- Cargo.lock | 154 ++++++---------------------- certs/server_cert.pem | 29 ++++++ certs/server_key.pem | 52 ++++++++++ certs/transport_cert.pem | 29 ++++++ certs/transport_key.pem | 52 ++++++++++ src/server/api.rs | 27 ++++- src/sql/user_online_tracker.rs | 40 ++++++++ src/transport/omikron_connection.rs | 1 + type-maps.yaml | 40 -------- 9 files changed, 259 insertions(+), 165 deletions(-) create mode 100644 certs/server_cert.pem create mode 100644 certs/server_key.pem create mode 100644 certs/transport_cert.pem create mode 100644 certs/transport_key.pem diff --git a/Cargo.lock b/Cargo.lock index 16c9bf5..c75fa4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,11 +1459,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] @@ -1473,9 +1471,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-client", "mtp-codec", @@ -2288,14 +2288,13 @@ dependencies = [ "mtp-crypto", "mtp-files", "mtp-host", - "mtp-transport", "mtp-type-map", ] [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-codec", "mtp-common", @@ -2308,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "base64", "byteorder", @@ -2321,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "quinn", "rustls", @@ -2332,7 +2331,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "base64", "chacha20poly1305", @@ -2351,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-crypto", "thiserror 1.0.69", @@ -2360,7 +2359,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-codec", "mtp-common", @@ -2373,7 +2372,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "log", "mtp-codec", @@ -2387,7 +2386,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "serde", "serde_yaml", @@ -2774,15 +2773,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.15" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", "rustls", @@ -2796,16 +2796,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2 0.6.4", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2836,20 +2836,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.2" @@ -2871,16 +2861,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -2896,21 +2876,21 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "rand_core" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rcgen" version = "0.14.8" @@ -4386,15 +4366,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -4428,30 +4399,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4464,12 +4418,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4482,12 +4430,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4500,24 +4442,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4530,12 +4460,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4548,12 +4472,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4566,12 +4484,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4584,12 +4496,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/certs/server_cert.pem b/certs/server_cert.pem new file mode 100644 index 0000000..23a13ba --- /dev/null +++ b/certs/server_cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCTCCAvGgAwIBAgIUFzlX/gHUsBzyka/qanwJ7SaH++gwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDcwNDE3MjM0MVoXDTI3MDcw +NDE3MjM0MVowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAkbTCZUXRNWmomFxNwI6xElPDGSX5xM05GWoOAYqW3teu +QV2dvBgX2DSywlVURnb6E3T+W+pZNEO95cL3R50+qPpVNHsW2VUbpaPBlFJ6F6ni +Pecx9mHSdawUkUwQsWd0DvTJbC557KH8kIrlc9oJOpneu6sLOoaBHyl1sucFI3vb +2Gl6C7MUGuh8olfseKmi/u32wohF6nuDTZvdmCIyrcvD2JEAgSfTqszr+T4JyEpF +DotzviPZFVQ2aVBQhjPUyio5vpvrgq6cELCGn7DZ95HCrU752Gb4VABUs/pEYRaZ +jY6+3OXoMrUolsGKlsupXnWz53OTjTqLb7XxjKoyfA6RgdMhF0C43Bl4uYIQgLhA +dJ1XCj1E/m28dmHvc1oSDzaYwBRCyYhG5/5B1GIU5+zEHsu/MSsmXZXe7PJmoWDm +kaFoYjdVYwV3EMF4VnrWGS6eTPSu62afBQAXogdwLf1YiUJppGBGZVl5PkPjQC9D +jAK0o9cudabBnV/KkjM7pZO5BOho4jpZ0ob9C+au+ocgjIyaQ8SPKJEh7ViN5VMg +B8wwf3f0oOt7Ok1T07Gqv8J75f4ouOfIoGgPr43Z2kHz63ga8yLEdFI5n9gEjLJb +jEgFyvI/1lVPPcO76OhF+UpeiQFf6eUjAyGMGia3E5YS0mNA3ANuhnNxhLg7Z8EC +AwEAAaNTMFEwHQYDVR0OBBYEFKyeBGfcdE4UJ0LH90q361b8Tk7bMB8GA1UdIwQY +MBaAFKyeBGfcdE4UJ0LH90q361b8Tk7bMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBAAhDfGdBEe72cbtsULnQ19zXB9xMhoG7PQiZFCG+aNl4sJ6q +Ep+ULXyyN/02IRnvrTTVp0bixMPZw7y2lkP5NJA5f/cXVDPXhNtLtua1EPHH98AL +SXJoLOPJ1EDuj4u7x4jxWDCZdbZicab9b+kzfEPHx+bwcHvxKCOGTHfu/MzCAiye +aYoop4nguOtgxpVhLODOd1kJp06ieI74tySdhbc+q5VfgRXSl3UyOgrgXVYuteW+ +kt+CoJrlKaZ+OK9N9jov+pNyriaccvXhrc5bc0s8QMwz4a3w8AJP0F4ubK4gYeUw +T3H1e0jqnWBy5yodhCjfyl3N7FVUHpMdBFdKvs3LUGiAdK7EhgpJd3pKdv8eGEB/ +cli/LQgf57kEjp7OJgapMNcEEwTM8XUgTsDzjxH3enh40mqbGhCSiwoMGPF0fldq +z5hWydIe0SQv39EbpCqQ+MPqK8Dk3vSXwh+bvh9dmvnUrPls2bQ1WbBsUz7VnwP0 +/RxgwhHCwDuNary6EinCLZZ1ZxDB4PMGfxcA8Zt4K7OOGfrCoP60HXbBcg2XJlrB +m2ThIXYDz6xX2FYXEfMIRbuEi717KuyKEnJl6+GFDNCKbpOVy4RXUcqwB/fCCyjg +b7oX2mrfCMCbMJiZl819eYFxaGcxv/tvFZa4rDnHkaWvKZRhu0pzIsJc6D6Q +-----END CERTIFICATE----- diff --git a/certs/server_key.pem b/certs/server_key.pem new file mode 100644 index 0000000..7db94e5 --- /dev/null +++ b/certs/server_key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQQIBADANBgkqhkiG9w0BAQEFAASCCSswggknAgEAAoICAQCRtMJlRdE1aaiY +XE3AjrESU8MZJfnEzTkZag4Bipbe165BXZ28GBfYNLLCVVRGdvoTdP5b6lk0Q73l +wvdHnT6o+lU0exbZVRulo8GUUnoXqeI95zH2YdJ1rBSRTBCxZ3QO9MlsLnnsofyQ +iuVz2gk6md67qws6hoEfKXWy5wUje9vYaXoLsxQa6HyiV+x4qaL+7fbCiEXqe4NN +m92YIjKty8PYkQCBJ9OqzOv5PgnISkUOi3O+I9kVVDZpUFCGM9TKKjm+m+uCrpwQ +sIafsNn3kcKtTvnYZvhUAFSz+kRhFpmNjr7c5egytSiWwYqWy6ledbPnc5ONOotv +tfGMqjJ8DpGB0yEXQLjcGXi5ghCAuEB0nVcKPUT+bbx2Ye9zWhIPNpjAFELJiEbn +/kHUYhTn7MQey78xKyZdld7s8mahYOaRoWhiN1VjBXcQwXhWetYZLp5M9K7rZp8F +ABeiB3At/ViJQmmkYEZlWXk+Q+NAL0OMArSj1y51psGdX8qSMzulk7kE6GjiOlnS +hv0L5q76hyCMjJpDxI8okSHtWI3lUyAHzDB/d/Sg63s6TVPTsaq/wnvl/ii458ig +aA+vjdnaQfPreBrzIsR0Ujmf2ASMsluMSAXK8j/WVU89w7vo6EX5Sl6JAV/p5SMD +IYwaJrcTlhLSY0DcA26Gc3GEuDtnwQIDAQABAoICAAq2ehZ9GCEvuAwfuT2/oAhD +bVRDhQYxupGCSS5W1LnkrLS0n9zM/q/QTBZatLwLkl5s2yjB5bYghu1GNO0ohAtj +gHE/3ZG0HrjCfgCcQcrMnExF29gMIC+zq711FMMeYpuY5thT4HYEAPBVrgIjFUug +NY6hyE0Qx5yVbUhukOOnfyX9t1H15DQIROWgxkum2UuaDmsGHvHbC+/gVl4RoZ1C +no8lNh7I4VitAWqNY6W8xDAKE8P2mcbmLqv2kj0TwYDtstovV5T/pozZlcRW+eeo +aWJZrKf7BiYS8idbbryer0KHrMPMCZL029udtkVZCwZdbBFH36DiHY9klrJrbxOw +gG2PmHgCAwXT4CQ2AOUXeSOWgjk1gH0q1baVWSPE4CbPC0Gl0Qlut8PgfrP5Mb/Q +3xLRjYcnNOVAZn0SLtX5Gg6TLcYg4HdAUdiP0ES9vkSWol+Fd1JVXUxVVvmK4AJI +Ynpm00X7YA89Mgd3NUE5M5ei26dyE9xWA99rmcJ8dQL0c9XAVKjPv7sMEf12KKDO +bLcR869zsVQuqNociHQEz48hYquGDkMmsmC28OET9eb9IO455my9AoQQZ00nJs4D ++nG0kDFtuq6lSp9EVq2ArJDqowrh8jWqi6yCCND/aFtiWLqeJJkK+ori2KB4LITi +wj03E6uGFwmXMD5VNVp5AoIBAQDNVoDmZdONaBmtrAm/63hxDGuTi1tCIQZe+E5A +O7SAjHMEeTcjVFC7ckdb52Q9kifAG7lw/mPiPcKa8fAFWCWyaOoyQotW2QVEJ7cR +5gBRVUrXozbfJEizRtqLmBwcucrFe72okgeQYa5QmnF1hUcqYmz/Mlole6LrN3Bc +zHznY7VPbSir1/SVGwDlVChl72Y5cdvwuSy4YgSM3sdLER3KlmvlkS1ItBSAR5Ow +/s7iANATSyZdzwyCqMw/KV86EqBtHXUao0G4hSo0PiVXpTME/rntu+KUegieJlRZ +CJ2zwcVvSmyo/Ubv0v/8HbW25jsCLD0yK2n/tB22/z1+OiCNAoIBAQC1p86+WUNo +xkMGMVxP8G4UWNLW2vkVWVPfipHarxcs1OvaCFWJDi0od4O+uDz8OjrJ7JpX8gqH +kjh2zw0zZ1vjMVoenOklJkO7x4iQNU44Abs+yGSyR3cSds992+S0yML89KKENakM +4f/VdETIza3e+2ASgJkitpmSpW8ofeEczFx3C5cpIDVfuXNP8i2HmOCDeVKP/c+m +NSt0jDZsIe1EZWDgwyJvwvfYzfDeEt9qzN75SFGbKYgVyjVIdxYMN18WB1S1MC6y +QdHcrFBYlQTCUXKGSAVE0uudAKMyQrZu6ZU6yJ3uwRbaqf4hqI0k01A1YNwMZ5Oo +Xs5OUdeUVxkFAoIBAFQs6/aHM6w6SpyJdA27B8rAHrGDZUu8ohH4lIt8bkhsSxJA +ZrwS6m2NMXeZadxbt/Pm6aRKhMPQMaC3IX/vXLTAkZE8+kYF/UStSz0JHoQ5BM9V +Vz8ubqcfmqOzHYMhHuDKEzuxcWxcvsrIJU7Z7gyQAMK074OCJY1rUqKYKXPpyf/f +bdbUVq1xPnLEhzMSasONy23pW03S0K62/NfnEmFkPKMFWpz9RZI4xXC/ZCv9zU67 +deTq5eQCMoiJ+0GhNSbF9qWO23g7DswP1dsSG9ImYi304bOgOzwp1lYRVCTGgJtY +1SaJfbT+uHWv8DgkNXh0jjms1m0+dxf3lkvSpFkCggEAagKiLoedRTnex3Bq9OPU ++d7UPDrR/dmBjwUpFTv1BF7FvRn8AN3bELCem+CdBn1c7ZY8hHJmHrQAa6VtiMAa +6b1VYGQl014/TQim87GIEjtAcOU1jq0SGh5n/IUlNTOsRz7k0M8X9sK1MHaH5b/S +ZjzA1ktiEtYJTYFqG3FKvCmvFHIZJxD61QA/ATrNw0I+U7urqQhv9Ak3cO6anAQ4 +mR+op8J5CEv5o/abSJ4u7/7baRMBPNHB7yVwbvfAUCTIQuzITGzv82th86xDv05H +1rb9ZMwG9yE4CwniM2SnXpbGVeXZ9QmOMtHd0vqwU/tV5bC++iZAD5Q0RZSRgKpL +mQKCAQA8ktcefAkcKTYOHbcHS/ARkIpjZeT2LA71pdv5Xa3FkW7pVAAg1e63qwSE +i/HVFMwzgClyjp+WMDirjm2rmpQ+qBzz0GuVMUjD+1kQd+KJ8vtGojYNKpJg2Aug ++C2uYVDsaOlIXVyuyhkJ5yfO4lb4JXLWYRdbQTnRX7oWpiy7kauFuUwKXo9sHqGD +5FHC/g4TQmV+uNPxWK4gnEiuVq2HLu4oVRhegwd3hZiDqPFj7wziIjMWZinSJ43C +616glIG/5F81ACaZeZJGOw+vkVzkuMeZMkhgPffYVD+TIFxl++fj7CRrH0yAKNpn +23BF0xaeMGypYNeF29nvifeI0XJF +-----END PRIVATE KEY----- diff --git a/certs/transport_cert.pem b/certs/transport_cert.pem new file mode 100644 index 0000000..2145708 --- /dev/null +++ b/certs/transport_cert.pem @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCTCCAvGgAwIBAgIUHjVeqHhryj/hji8tNx6fFCnYZkMwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI2MDcwNDE3MjM0NFoXDTI3MDcw +NDE3MjM0NFowFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEAqyiBICCnWqU+80udT0dm/+zixAehbp8mlDOWeQJzizCT +qVGPdrgn6HxZKuSCQ+AYdCvMrTjkv0fTMnwwk1Dlwzt/WYn5QSXYFaoMJZF3vEka +mOCFgVlahYx0Krz6wO0n8xfeDd1qSwAyGCk7JXnlTTnbht8tfdbm2RQn2dq0oQkw +DLtdfNTB1FcnjAy1UpYJOow6okuKFyngGU/U/sd+cIPQP9+7TRtSrnkRYSbpVApM +rQgqXDMSxEu7k6dd9K3zgp23zwAzCuLJ1L+aNCKyMP6Kh3qlZYIcBVzKFAmkJniy +XfRTyCdvsA6Lb6HyaKt8TLsxcdBkYuy7xdvFlLwVe9Afag1VDuw/UNwviJZVHMLi +cox02u+ICCPssxmt7bzc0v70WPBF8OsdU1zpE5MP1wtO2o1XlEH+DszkyfZk1pPs +ouWEJEa/imrsjrch0x7dvMzdo3vJT9izfaDwR9gkDdNSkeHYOwUPdgUPJlICPhVy +Ny6FA+2V1ktZE5MdbvNw9EjjIZueIc9Ilp90UHUDMSvS6zD3vABh2LfaEG1GaT/I +2x1PT5weVdHGH01SnV95k2tMBH8lK6v8alzoQUwqdmTCLIs4N2oZQgS3D564v/UX +iEtTEmyPPa7bdazIRwRuRK0Q59+qvAAHXZzzw5dw2q3Y9+AyOQvXYreRQqluZCcC +AwEAAaNTMFEwHQYDVR0OBBYEFDRbK+KovS24EIioPNSY1zr4KdwkMB8GA1UdIwQY +MBaAFDRbK+KovS24EIioPNSY1zr4KdwkMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI +hvcNAQELBQADggIBAAwa+2LD5a/g9aw9S9oCQT0y2v8tBAhlfDtaD4QhN5lSItEC +3ReagJ5w2ISH164PCpMU4keFDeh1dK/j/7hf0muH2KF6j8ByfMua2h/njWymvOE4 +hksFzfJmBlUImXdzUci0Dii68iAzwt2aNHFUCHyQ/uZ6PKN5cSATcOC4GdSImOkT +7Via1kop+8NGWdbtbKzenpD+6vMET3+9rDWafRmIj42vMyXE+f795KCWAlXYo6T7 +hI4PWtVqJZX+k6LDB7PMGokNDCusjNrYLoMk8pWM2GAScMmoXakGyNqMAOPQDAZW +UQ6LUn/oooC/F9oE5ZUoSaq+iM7B7TVX13EI+ccY411CRS95PZzG+zCI6sCuGNh0 +OJ7WbvqR/snkrK/hu3mKZr2FuYtKJQO7KMSqDwkv80mowQDektVIqj226z6wVjvU +0uObwtRUMWqpfiY4tYqpyI6AXogTwAytHqltGL/thcBK7EW7Wi78kSwBDu3qXTzs +Iwbq+kOuRci5qGMeUerHsRWDDvwict8Fqf2xKw8RNvOkkWg4UxEZsemCpHoCbAPb +Piw5+HSDAqBRM03bzi6T1Gf847+uq6MA7l5qJZjEaRvmJoBlaS80bztYg5MOFGxL +WhVM9TwUHcGaysnd0urArsED1L1AJ4f9VlnwTnjUNZdnW/wd4airkP4FW4Ol +-----END CERTIFICATE----- diff --git a/certs/transport_key.pem b/certs/transport_key.pem new file mode 100644 index 0000000..c8da87b --- /dev/null +++ b/certs/transport_key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCrKIEgIKdapT7z +S51PR2b/7OLEB6FunyaUM5Z5AnOLMJOpUY92uCfofFkq5IJD4Bh0K8ytOOS/R9My +fDCTUOXDO39ZiflBJdgVqgwlkXe8SRqY4IWBWVqFjHQqvPrA7SfzF94N3WpLADIY +KTsleeVNOduG3y191ubZFCfZ2rShCTAMu1181MHUVyeMDLVSlgk6jDqiS4oXKeAZ +T9T+x35wg9A/37tNG1KueRFhJulUCkytCCpcMxLES7uTp130rfOCnbfPADMK4snU +v5o0IrIw/oqHeqVlghwFXMoUCaQmeLJd9FPIJ2+wDotvofJoq3xMuzFx0GRi7LvF +28WUvBV70B9qDVUO7D9Q3C+IllUcwuJyjHTa74gII+yzGa3tvNzS/vRY8EXw6x1T +XOkTkw/XC07ajVeUQf4OzOTJ9mTWk+yi5YQkRr+KauyOtyHTHt28zN2je8lP2LN9 +oPBH2CQN01KR4dg7BQ92BQ8mUgI+FXI3LoUD7ZXWS1kTkx1u83D0SOMhm54hz0iW +n3RQdQMxK9LrMPe8AGHYt9oQbUZpP8jbHU9PnB5V0cYfTVKdX3mTa0wEfyUrq/xq +XOhBTCp2ZMIsizg3ahlCBLcPnri/9ReIS1MSbI89rtt1rMhHBG5ErRDn36q8AAdd +nPPDl3Dardj34DI5C9dit5FCqW5kJwIDAQABAoICABp/REeW1oxuwvPuXlWnfldw +3bBZurusnCUwZq39hlQOOqXpUFZMRIgLjCWwR5xoxSW4TCZQWwAOh1ssT7wsDM3w +gugijI8cjpCCtAEIPVWi/vZGN/+M4jnaQyJaAaXmxnBYtGdIF7cHD4+e/nRFo+KU +lPA90nCbWZSnYqipi5zXQR7kziB6XYwudpsKB/OCR755IXCo/3ao5HPMC0jq1cpn +FaZ10AIsEIed4DtpRzuxS9Uzy5Rc0chy2FYPqzjVyeThiX2x5bUHOWtxbYF/EkAJ +1U9hBNmrTq7j17hKFSVhj8Is24rufq4Y/6wnZvxI+WQgTL4hHrwT3dlP4I2En/yQ +cWeblqclTB04O7L4fy518bZ9TQj+u07UeccWW4ZJvGEejlwWZzY5oLDDeQ7LUb2r +K/U/XGn3mYArKfW0GB7JpiALkIi9KU4Zz9LXObkFxYQug1WTSHH1Ge10W88D2cDM +w8zZanq1TudCLEeWghosXglSvyPzNmJg/g4l6tfTYL9n5ISS09sZWUit54zIS83h +TQtdCjcO9jXSovzLPnHhjtyV9s4WX0wNTbDmpJ41vWVVpYYII5b0ljLMFUGkj6bU +/LC6K/VV1u08OK2A3tZ9ZxLVt8kXEISiOkMfdnNImdtoNEWZY0jglW99bt5uBZ6u +l86o3yqGPIHK31ByBjblAoIBAQDj5Jxe4+xGQaTtsgNaaISbp+qPGo6HoOEmpDo6 +cWGcZRLKuQhyLIg9ANJg33aISkIYjkKs08DegjAST9m0QmgXeOQzWf9yZhSA/HZN +Znk1jyNiO3d8ZEBSRcIxBgKF546KCbCdIZGIwwRS2rNHRMYvA3u/OC8T+S2MZ2Bb +S7wysBP0z6vPBmmo491OufmovGD2mrPFFFAKAvfvbnUXVe+NQNZPAbZgL6rq7RfL +8Y4rr7TTY1wy6ocAhr8opsl5kMOJ2dPu9e+gpR42kYKAFcDFS7DzyUVkOTQTrpIH +JjcuGnGED6zdEDiybfzoe6LlOVgg5BaaE8Qpa/gHEb6jUnirAoIBAQDARJNLburc +k/uL9hYpdGgme+8vIolfUhNgfzc17T9xvo0j5hB7DjG5f1Cy62zMHZZWlXg0+8dp +VZUCYWjduDuh8eZxuWZEnkdPvzAIfX0W0xtChLLY5xzzmzlgKeerJJ2RhpLXgEEj +vnUkfyJCL9lXwcY+Zr+Jcg+ckB2n5mltYosai3GLB2xjzG5YoeFa6lIjOP+tc6qk +iDr6iTOVixTdrAaEg8mKYsQrSajyxXt9OUVxOjyKTzafzG6aMAXXcABPS0NOL7Jk +pjgnxedPc7t6mFLxLnc+RE6Y0ZuUIf6i1oJ++47qTtxO6FKdvjJCqi4hqFlbZwJ8 +IM4OZXwtM7p1AoIBACVpl35Afr598lyPV9uUwwhmxawemP2NHMliNqj3iiXJtg8P +12IfC3KwWtlKdkQjpLwxRqLm1Ify6X9oGn5sVHheAWCoyrX2KjGOwMEuLIHtcMqk +ppYGQ0T+bMXtjaZ8ADuLIbJ6G1FPartLahkuIjb/pGYdDCWER8UAB6KVSV0s6OnM +2hDPpbLzTZaqE2FvI5/4f8C/9+/c0uU8xf5BvmT7GvO3fUBHMxEEQV7a1hM38kCt +1CoLtIQSmvlKgFLzwBY2a4OUXnWKBe7BbqImcBIzveYXycuNVI5Sn2iLDo4EC1S5 +2hAkpJouUlG8Ul69TkmACHHdAtwl/H70m85mMKcCggEBALLCD0BfC7WxUHq8fbZl +KEA/kg38ykP5h1J8dNSKlCYoyAjMah4/ADQ7DsiK4kiUaaZuqah5PtnMXBIuhtU9 +6ROUWHD7sv2xOxQF6Z67LYX/GKUrwyyvE9faReoGjAIutOEMgZIjCG03MqqQGUX4 +Pta8rpht5z7e8WzbAPeN3g95Q2OWqiZc7IWNGDsHpwJkQaHF6NFXbRw0HMG6tl8f +RHMk8Zpa/RLQ6RpmDdJJkZrHmKzBPHBLXboM0/g/Fe9Ljr4qd5VF+1JgyG0SLxFL +xM8iuFrMnMkjsP+cQk3jnV/AmOt+700hrOsgTU+j+g7JMsLQRuQYTdMinPH9oQpV +wdECggEBAJGFsteTj6E9D926+URGP07jYmdZJ7Vln8cKiIOWEEsCmjWBLILtomMO +XhdlJvR992reHBabNJa4ma3LwA1A7sbaMEIr0u+XzmU7E7HiBcBb8OA/RXy5Objo +4jVisvx331LMKSAv0HMnDVDFhyC3r3VxgQUEptolqS0QHDM5i6/N0CgWFlYUJAzY +R4/CXpZt8mLgCk9HJT3+6aAfSlkcBjruevX03xVmzBBFoOIwHRjxqNCi/4gEl4Os +w41FMqoV6JhksXN11aLRwRKLtNEkBxR3g4BEBidk+2bC+uJFqlWlFGFIUq2RKURB +tXVzHj9k6OcU/WVyVApnTjE757+2yJ4= +-----END PRIVATE KEY----- diff --git a/src/server/api.rs b/src/server/api.rs index faf11b7..c8aeeb9 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,7 +1,7 @@ use crate::load_keyring; use crate::sql::sql; use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; -use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; +use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use actix_web::HttpResponse; @@ -151,6 +151,31 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } } + ["api", "get", "connections"] => { + if let Ok(connections) = get_all_connections().await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + + for (omikron_id, iota_map) in connections { + let mut omikron_obj = JsonValue::new_object(); + for (iota_id, user_ids) in iota_map { + let mut user_arr = JsonValue::new_array(); + for user_id in user_ids { + let _ = user_arr.push(user_id); + } + omikron_obj[&iota_id.to_string()] = user_arr; + } + res[&omikron_id.to_string()] = omikron_obj; + } + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } + // ================================================== // GET IOTA BY ID // ================================================== diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 304d5b5..288fcdc 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -67,6 +67,46 @@ pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) } +pub async fn get_all_connections() +-> Result>>, ()> { + let mut result: std::collections::HashMap>> = + std::collections::HashMap::new(); + + for entry in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *entry.key(); + for omikron_id in entry.value().iter() { + result + .entry(*omikron_id) + .or_insert_with(std::collections::HashMap::new) + .entry(iota_id) + .or_insert_with(Vec::new); + } + } + + for entry in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *entry.key(); + let omikron_ids = entry.value().clone(); + + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + for user in users { + let user_id = user.0 as i64; + if let Some(conn) = USER_STATUS_MAP.get(&user_id) { + let user_omikron_id = conn.omikron_id; + if omikron_ids.contains(&user_omikron_id) { + if let Some(iota_map) = result.get_mut(&user_omikron_id) { + if let Some(user_vec) = iota_map.get_mut(&iota_id) { + user_vec.push(user_id); + } + } + } + } + } + } + } + + Ok(result) +} + pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { USER_STATUS_MAP.insert( user_id, diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 2b2ba69..2da779d 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -304,6 +304,7 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::IotaUserData) .with_id(cv.get_id()) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); let _ = self.send(&response).await; diff --git a/type-maps.yaml b/type-maps.yaml index 73a15e3..6d51730 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -1,46 +1,6 @@ # The version a Client should use protocol_version: "1.0" -# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended -# Fixed CommunicationType markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorBadVersion: 2 -# Disconnect: 3 -# Redirect: 4 -# Shutdown: 5 -# BadRequest: 6 -# Unauthorized: 7 -# Forbidden: 8 -# NotFound: 9 -# TooManyRequests: 10 -# InternalServerError: 11 -# BadGateway: 12 -# ServiceUnavailable: 13 -# GatewayTimeout: 14 -# Identification: 15 -# IdentificationResponse: 16 -# Register: 17 -# RegisterResponse: 18 -# Ping: 19 -# Pong: 20 -# -# Fixed Data Type markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorMessage: 2 -# Version: 3 -# Description: 4 -# Timestamp: 5 -# Id: 6 -# ClientNonce: 7 -# ServerNonce: 8 -# PublicKeys: 9 -# Signature: 10 -# Connected: 11 -# -# If a Type can't be used it will be mapped to 0 - type_maps: "1.0": # Protocol version 1.0 CommunicationTypes: From 73027e6474b06459183c5f3c9bba6e32f13ac19f Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sat, 4 Jul 2026 23:02:04 +0200 Subject: [PATCH 170/220] [Fix]Connection Stability --- Cargo.lock | 154 ++++++---------------------- src/server/api.rs | 27 ++++- src/sql/user_online_tracker.rs | 40 ++++++++ src/transport/omikron_connection.rs | 1 + type-maps.yaml | 40 -------- 5 files changed, 97 insertions(+), 165 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 16c9bf5..c75fa4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1459,11 +1459,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", - "js-sys", "libc", "r-efi 5.3.0", "wasip2", - "wasm-bindgen", ] [[package]] @@ -1473,9 +1471,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", "r-efi 6.0.0", "rand_core 0.10.1", + "wasm-bindgen", ] [[package]] @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-client", "mtp-codec", @@ -2288,14 +2288,13 @@ dependencies = [ "mtp-crypto", "mtp-files", "mtp-host", - "mtp-transport", "mtp-type-map", ] [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-codec", "mtp-common", @@ -2308,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "base64", "byteorder", @@ -2321,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "quinn", "rustls", @@ -2332,7 +2331,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "base64", "chacha20poly1305", @@ -2351,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-crypto", "thiserror 1.0.69", @@ -2360,7 +2359,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "mtp-codec", "mtp-common", @@ -2373,7 +2372,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "log", "mtp-codec", @@ -2387,7 +2386,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#b96c072a0f87de7828e45cf3dcd44aef4a9e459d" +source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" dependencies = [ "serde", "serde_yaml", @@ -2774,15 +2773,16 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.15" +version = "0.11.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "aws-lc-rs", "bytes", - "getrandom 0.3.4", + "getrandom 0.4.3", "lru-slab", - "rand 0.9.4", + "rand 0.10.2", + "rand_pcg", "ring", "rustc-hash", "rustls", @@ -2796,16 +2796,16 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" dependencies = [ "cfg_aliases", "libc", "once_cell", "socket2 0.6.4", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2836,20 +2836,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.2" @@ -2871,16 +2861,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -2896,21 +2876,21 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "rand_core" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "rcgen" version = "0.14.8" @@ -4386,15 +4366,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -4428,30 +4399,13 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", + "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4464,12 +4418,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4482,12 +4430,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4500,24 +4442,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - [[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_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4530,12 +4460,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4548,12 +4472,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4566,12 +4484,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4584,12 +4496,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - [[package]] name = "wit-bindgen" version = "0.57.1" diff --git a/src/server/api.rs b/src/server/api.rs index faf11b7..c8aeeb9 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,7 +1,7 @@ use crate::load_keyring; use crate::sql::sql; use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; -use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; +use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use actix_web::HttpResponse; @@ -151,6 +151,31 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } } + ["api", "get", "connections"] => { + if let Ok(connections) = get_all_connections().await { + let mut res = JsonValue::new_object(); + res["status"] = "success".into(); + + for (omikron_id, iota_map) in connections { + let mut omikron_obj = JsonValue::new_object(); + for (iota_id, user_ids) in iota_map { + let mut user_arr = JsonValue::new_array(); + for user_id in user_ids { + let _ = user_arr.push(user_id); + } + omikron_obj[&iota_id.to_string()] = user_arr; + } + res[&omikron_id.to_string()] = omikron_obj; + } + + (StatusCode::OK, res.dump()) + } else { + let mut res = JsonValue::new_object(); + res["status"] = "error".into(); + (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) + } + } + // ================================================== // GET IOTA BY ID // ================================================== diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 304d5b5..288fcdc 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -67,6 +67,46 @@ pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) } +pub async fn get_all_connections() +-> Result>>, ()> { + let mut result: std::collections::HashMap>> = + std::collections::HashMap::new(); + + for entry in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *entry.key(); + for omikron_id in entry.value().iter() { + result + .entry(*omikron_id) + .or_insert_with(std::collections::HashMap::new) + .entry(iota_id) + .or_insert_with(Vec::new); + } + } + + for entry in IOTA_OMIKRON_CONNECTIONS.iter() { + let iota_id = *entry.key(); + let omikron_ids = entry.value().clone(); + + if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + for user in users { + let user_id = user.0 as i64; + if let Some(conn) = USER_STATUS_MAP.get(&user_id) { + let user_omikron_id = conn.omikron_id; + if omikron_ids.contains(&user_omikron_id) { + if let Some(iota_map) = result.get_mut(&user_omikron_id) { + if let Some(user_vec) = iota_map.get_mut(&iota_id) { + user_vec.push(user_id); + } + } + } + } + } + } + } + + Ok(result) +} + pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { USER_STATUS_MAP.insert( user_id, diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 2b2ba69..2da779d 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -304,6 +304,7 @@ impl OmikronConnection { let response = CommunicationValue::new(CommunicationType::IotaUserData) .with_id(cv.get_id()) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); let _ = self.send(&response).await; diff --git a/type-maps.yaml b/type-maps.yaml index 73a15e3..6d51730 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -1,46 +1,6 @@ # The version a Client should use protocol_version: "1.0" -# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended -# Fixed CommunicationType markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorBadVersion: 2 -# Disconnect: 3 -# Redirect: 4 -# Shutdown: 5 -# BadRequest: 6 -# Unauthorized: 7 -# Forbidden: 8 -# NotFound: 9 -# TooManyRequests: 10 -# InternalServerError: 11 -# BadGateway: 12 -# ServiceUnavailable: 13 -# GatewayTimeout: 14 -# Identification: 15 -# IdentificationResponse: 16 -# Register: 17 -# RegisterResponse: 18 -# Ping: 19 -# Pong: 20 -# -# Fixed Data Type markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorMessage: 2 -# Version: 3 -# Description: 4 -# Timestamp: 5 -# Id: 6 -# ClientNonce: 7 -# ServerNonce: 8 -# PublicKeys: 9 -# Signature: 10 -# Connected: 11 -# -# If a Type can't be used it will be mapped to 0 - type_maps: "1.0": # Protocol version 1.0 CommunicationTypes: From 088d39b73c3ea585fd0d128289fac9427dece93a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:38:28 +0200 Subject: [PATCH 171/220] [Upd] MTP Cargo Update --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c75fa4a..1202ca1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,9 +674,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.65" +version = "1.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" dependencies = [ "find-msvc-tools", "jobserver", @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-client", "mtp-codec", @@ -2294,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "base64", "byteorder", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "quinn", "rustls", @@ -2331,7 +2331,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "base64", "chacha20poly1305", @@ -2350,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-crypto", "thiserror 1.0.69", @@ -2359,7 +2359,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-codec", "mtp-common", @@ -2372,7 +2372,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "log", "mtp-codec", @@ -2386,7 +2386,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "serde", "serde_yaml", @@ -2404,9 +2404,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", From 144a1966933acad9bde5469086f47e59aa6ad871 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:38:28 +0200 Subject: [PATCH 172/220] [Upd] MTP Cargo Update --- Cargo.lock | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c75fa4a..1202ca1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,9 +674,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.65" +version = "1.2.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" dependencies = [ "find-msvc-tools", "jobserver", @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-client", "mtp-codec", @@ -2294,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "base64", "byteorder", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "quinn", "rustls", @@ -2331,7 +2331,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "base64", "chacha20poly1305", @@ -2350,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-crypto", "thiserror 1.0.69", @@ -2359,7 +2359,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "mtp-codec", "mtp-common", @@ -2372,7 +2372,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "log", "mtp-codec", @@ -2386,7 +2386,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2e7c0b489360d5d05fe53326811616e730c1a556" +source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" dependencies = [ "serde", "serde_yaml", @@ -2404,9 +2404,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" dependencies = [ "num-integer", "num-traits", From c9deb8e411fe6790baa7b13edcf76c9f573bc0c2 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:50:13 +0200 Subject: [PATCH 173/220] types --- type-maps.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/type-maps.yaml b/type-maps.yaml index 6d51730..0eebb7f 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -99,6 +99,11 @@ type_maps: AppChallengeResponse: 133 AppIdentificationResponse: 134 LoadTxtRecord: 135 + ErrorNotSet: 136 + SetChatSecret: 139 + GetChatSecret: 140 + ChatSecretResponse: 141 + ChatSecretForward: 142 DataTypes: ErrorType: 32 ErrorProtocol: 33 @@ -198,3 +203,19 @@ type_maps: AppSession: 130 AppData: 131 TauriToken: 132 + Challenge: 133 + EncryptedPayload: 134 + SecurePayload: 135 + DeviceId: 136 + ClientId: 137 + SecretId: 142 + VersionNumber: 143 + EncryptedSecret: 144 + WrappingScheme: 146 + UpdatedAt: 147 + ChatId: 148 + KemCiphertext: 149 + SenderUserId: 152 + RecipientUserId: 153 + Recipients: 154 + ReplyId: 155 From f5f92f68473c13b427c4671922b0e3a694947ab8 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:50:13 +0200 Subject: [PATCH 174/220] types --- type-maps.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/type-maps.yaml b/type-maps.yaml index 6d51730..0eebb7f 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -99,6 +99,11 @@ type_maps: AppChallengeResponse: 133 AppIdentificationResponse: 134 LoadTxtRecord: 135 + ErrorNotSet: 136 + SetChatSecret: 139 + GetChatSecret: 140 + ChatSecretResponse: 141 + ChatSecretForward: 142 DataTypes: ErrorType: 32 ErrorProtocol: 33 @@ -198,3 +203,19 @@ type_maps: AppSession: 130 AppData: 131 TauriToken: 132 + Challenge: 133 + EncryptedPayload: 134 + SecurePayload: 135 + DeviceId: 136 + ClientId: 137 + SecretId: 142 + VersionNumber: 143 + EncryptedSecret: 144 + WrappingScheme: 146 + UpdatedAt: 147 + ChatId: 148 + KemCiphertext: 149 + SenderUserId: 152 + RecipientUserId: 153 + Recipients: 154 + ReplyId: 155 From 0a32e5f41b6b50b2824b780d6dd2d831493659da Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:37:16 +0200 Subject: [PATCH 175/220] Typemap --- Cargo.lock | 53 ++++++++++++++++++++++++++------------------------ type-maps.yaml | 53 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1202ca1..6dfdfc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -901,18 +901,18 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" @@ -1997,11 +1997,11 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-client", "mtp-codec", @@ -2294,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "base64", "byteorder", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "quinn", "rustls", @@ -2331,7 +2331,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "base64", "chacha20poly1305", @@ -2350,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-crypto", "thiserror 1.0.69", @@ -2359,7 +2359,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-codec", "mtp-common", @@ -2372,11 +2372,12 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "log", "mtp-codec", "mtp-common", + "rcgen", "rustls", "rustls-native-certs", "tokio", @@ -2386,7 +2387,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "serde", "serde_yaml", @@ -2445,11 +2446,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -2898,6 +2898,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ "aws-lc-rs", + "pem", + "ring", "rustls-pki-types", "time", "x509-parser", @@ -3168,9 +3170,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -4580,6 +4582,7 @@ dependencies = [ "lazy_static", "nom", "oid-registry", + "ring", "rusticata-macros", "thiserror 2.0.18", "time", @@ -4620,18 +4623,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.52" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.52" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" dependencies = [ "proc-macro2", "quote", diff --git a/type-maps.yaml b/type-maps.yaml index 0eebb7f..dddd7a2 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -1,8 +1,46 @@ -# The version a Client should use protocol_version: "1.0" +# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended + +# Fixed CommunicationType markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorBadVersion: 2 +# Disconnect: 3 +# Redirect: 4 +# Shutdown: 5 +# BadRequest: 6 +# Unauthorized: 7 +# Forbidden: 8 +# NotFound: 9 +# TooManyRequests: 10 +# InternalServerError: 11 +# BadGateway: 12 +# ServiceUnavailable: 13 +# GatewayTimeout: 14 +# Identification: 15 +# IdentificationResponse: 16 +# Register: 17 +# RegisterResponse: 18 +# Ping: 19 +# Pong: 20 + +# Fixed Data Type markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorMessage: 2 +# Version: 3 +# Description: 4 +# Timestamp: 5 +# Id: 6 +# ClientNonce: 7 +# ServerNonce: 8 +# PublicKeys: 9 +# Signature: 10 +# Connected: 11 + type_maps: - "1.0": # Protocol version 1.0 + "1.0": CommunicationTypes: ErrorProtocol: 33 ErrorAnonymous: 34 @@ -33,6 +71,7 @@ type_maps: MessageLive: 59 MessageOtherIota: 60 MessageChunk: 61 + MessageGet: 143 MessagesGet: 62 PushNotification: 63 ReadNotification: 64 @@ -104,6 +143,11 @@ type_maps: GetChatSecret: 140 ChatSecretResponse: 141 ChatSecretForward: 142 + MessageEditLive: 144 + MessageEdit: 145 + MessageReactionAdd: 146 + MessageReactionRemove: 147 + MessageReactionLive: 148 DataTypes: ErrorType: 32 ErrorProtocol: 33 @@ -218,4 +262,7 @@ type_maps: SenderUserId: 152 RecipientUserId: 153 Recipients: 154 - ReplyId: 155 + Edited: 155 + Reactions: 156 + Reaction: 157 + ReplyId: 158 From f7c52c30a7456577b33e3470f1464880761e418b Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:37:16 +0200 Subject: [PATCH 176/220] Typemap --- Cargo.lock | 53 ++++++++++++++++++++++++++------------------------ type-maps.yaml | 53 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 78 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1202ca1..6dfdfc9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -901,18 +901,18 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.12" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.21" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" @@ -1997,11 +1997,11 @@ dependencies = [ [[package]] name = "jobserver" -version = "0.1.34" +version = "0.1.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.3", "libc", ] @@ -2280,7 +2280,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-client", "mtp-codec", @@ -2294,7 +2294,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-codec", "mtp-common", @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "base64", "byteorder", @@ -2320,7 +2320,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "quinn", "rustls", @@ -2331,7 +2331,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "base64", "chacha20poly1305", @@ -2350,7 +2350,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-crypto", "thiserror 1.0.69", @@ -2359,7 +2359,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "mtp-codec", "mtp-common", @@ -2372,11 +2372,12 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "log", "mtp-codec", "mtp-common", + "rcgen", "rustls", "rustls-native-certs", "tokio", @@ -2386,7 +2387,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#515244ce6664b355f0a89b00c8c18c26c8b95dbd" +source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" dependencies = [ "serde", "serde_yaml", @@ -2445,11 +2446,10 @@ dependencies = [ [[package]] name = "num-iter" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -2898,6 +2898,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" dependencies = [ "aws-lc-rs", + "pem", + "ring", "rustls-pki-types", "time", "x509-parser", @@ -3168,9 +3170,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" @@ -4580,6 +4582,7 @@ dependencies = [ "lazy_static", "nom", "oid-registry", + "ring", "rusticata-macros", "thiserror 2.0.18", "time", @@ -4620,18 +4623,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.52" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.52" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" dependencies = [ "proc-macro2", "quote", diff --git a/type-maps.yaml b/type-maps.yaml index 0eebb7f..dddd7a2 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -1,8 +1,46 @@ -# The version a Client should use protocol_version: "1.0" +# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended + +# Fixed CommunicationType markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorBadVersion: 2 +# Disconnect: 3 +# Redirect: 4 +# Shutdown: 5 +# BadRequest: 6 +# Unauthorized: 7 +# Forbidden: 8 +# NotFound: 9 +# TooManyRequests: 10 +# InternalServerError: 11 +# BadGateway: 12 +# ServiceUnavailable: 13 +# GatewayTimeout: 14 +# Identification: 15 +# IdentificationResponse: 16 +# Register: 17 +# RegisterResponse: 18 +# Ping: 19 +# Pong: 20 + +# Fixed Data Type markers are: +# Error: 0 +# ErrorParsing: 1 +# ErrorMessage: 2 +# Version: 3 +# Description: 4 +# Timestamp: 5 +# Id: 6 +# ClientNonce: 7 +# ServerNonce: 8 +# PublicKeys: 9 +# Signature: 10 +# Connected: 11 + type_maps: - "1.0": # Protocol version 1.0 + "1.0": CommunicationTypes: ErrorProtocol: 33 ErrorAnonymous: 34 @@ -33,6 +71,7 @@ type_maps: MessageLive: 59 MessageOtherIota: 60 MessageChunk: 61 + MessageGet: 143 MessagesGet: 62 PushNotification: 63 ReadNotification: 64 @@ -104,6 +143,11 @@ type_maps: GetChatSecret: 140 ChatSecretResponse: 141 ChatSecretForward: 142 + MessageEditLive: 144 + MessageEdit: 145 + MessageReactionAdd: 146 + MessageReactionRemove: 147 + MessageReactionLive: 148 DataTypes: ErrorType: 32 ErrorProtocol: 33 @@ -218,4 +262,7 @@ type_maps: SenderUserId: 152 RecipientUserId: 153 Recipients: 154 - ReplyId: 155 + Edited: 155 + Reactions: 156 + Reaction: 157 + ReplyId: 158 From e121e947cfe71317d11bfb2faf1e3dd0b2c7936a Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 13 Jul 2026 17:30:39 +0200 Subject: [PATCH 177/220] edits & reactions --- type-maps.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/type-maps.yaml b/type-maps.yaml index dddd7a2..3659921 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -148,6 +148,7 @@ type_maps: MessageReactionAdd: 146 MessageReactionRemove: 147 MessageReactionLive: 148 + MessageDeleteLive: 150 DataTypes: ErrorType: 32 ErrorProtocol: 33 From 6c2211f8c43067629d72e557b46aa3ef9efc4ea5 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 13 Jul 2026 17:30:39 +0200 Subject: [PATCH 178/220] edits & reactions --- type-maps.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/type-maps.yaml b/type-maps.yaml index dddd7a2..3659921 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -148,6 +148,7 @@ type_maps: MessageReactionAdd: 146 MessageReactionRemove: 147 MessageReactionLive: 148 + MessageDeleteLive: 150 DataTypes: ErrorType: 32 ErrorProtocol: 33 From 70015c4d69a5a92cdd83ff25368e2585c15a73bc Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:28:13 +0200 Subject: [PATCH 179/220] [Upd] mtp update --- Cargo.lock | 1005 ++++++++++----------------- Cargo.toml | 9 +- src/main.rs | 28 +- src/server/api.rs | 88 ++- src/server/index.rs | 10 +- src/server/server.rs | 84 +-- src/sql/sql.rs | 11 +- src/transport/omikron_connection.rs | 94 +-- type-maps.yaml | 2 +- 9 files changed, 482 insertions(+), 849 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6dfdfc9..4ce5e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,210 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "actix-codec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" -dependencies = [ - "bitflags 2.13.0", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "base64", - "bitflags 2.13.0", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "foldhash 0.2.0", - "futures-core", - "h2 0.3.27", - "http 0.2.12", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.10.2", - "sha1 0.11.0", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" -dependencies = [ - "bytestring", - "cfg-if", - "http 0.2.12", - "regex", - "regex-lite", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.10", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "impl-more 0.1.9", - "pin-project-lite", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-tls", - "actix-utils", - "actix-web-codegen", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "foldhash 0.2.0", - "futures-core", - "futures-util", - "impl-more 0.3.1", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "regex-lite", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.6.4", - "time", - "tracing", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "adler2" version = "2.0.1" @@ -268,30 +64,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -309,9 +81,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" @@ -334,7 +106,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", ] @@ -346,7 +118,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -358,7 +130,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -392,7 +164,7 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.4.1", + "fastrand 2.5.0", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -503,6 +275,17 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-trait" +version = "0.1.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.1", +] + [[package]] name = "atoi" version = "2.0.0" @@ -526,9 +309,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.1" +version = "1.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -537,9 +320,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.42.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" dependencies = [ "cc", "cmake", @@ -577,9 +360,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" dependencies = [ "serde_core", ] @@ -615,27 +398,6 @@ dependencies = [ "piper", ] -[[package]] -name = "brotli" -version = "8.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bumpalo" version = "3.20.3" @@ -650,18 +412,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" - -[[package]] -name = "bytestring" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" -dependencies = [ - "bytes", -] +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bzip2" @@ -674,9 +427,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.66" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ "find-msvc-tools", "jobserver", @@ -692,9 +445,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" @@ -805,26 +558,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -978,7 +711,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1020,9 +753,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid 0.10.2", "zeroize", @@ -1056,30 +789,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", + "syn 2.0.119", ] [[package]] @@ -1114,7 +824,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1242,6 +952,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastbloom" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" +dependencies = [ + "foldhash 0.2.0", + "libm", + "portable-atomic", + "siphasher", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1253,9 +975,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fiat-crypto" @@ -1281,7 +1003,6 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ - "crc32fast", "miniz_oxide", "zlib-rs", ] @@ -1331,10 +1052,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] -name = "futures-channel" -version = "0.3.32" +name = "futures" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1342,15 +1078,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -1370,9 +1106,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-lite" @@ -1395,7 +1131,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.4.1", + "fastrand 2.5.0", "futures-core", "futures-io", "parking", @@ -1403,25 +1139,38 @@ dependencies = [ ] [[package]] -name = "futures-sink" -version = "0.3.32" +name = "futures-macro" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1499,25 +1248,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.15" @@ -1529,7 +1259,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.2", + "http", "indexmap", "slab", "tokio", @@ -1537,6 +1267,62 @@ dependencies = [ "tracing", ] +[[package]] +name = "h3" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" +dependencies = [ + "bytes", + "fastrand 2.5.0", + "futures-util", + "http", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "h3-datagram" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2c9f77921668673721ae40f17c729fc48b9e38a663858097cea547484fdf0f" +dependencies = [ + "bytes", + "h3", + "pin-project-lite", +] + +[[package]] +name = "h3-quinn" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" +dependencies = [ + "bytes", + "futures", + "h3", + "h3-datagram", + "quinn", + "tokio", + "tokio-util", +] + +[[package]] +name = "h3-webtransport" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d91a50fd582a5d67b1f756fba3cd9c66367ff4f23e1017c882f664d63b350a7" +dependencies = [ + "bytes", + "futures-util", + "h3", + "h3-datagram", + "http", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1644,17 +1430,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.2" @@ -1667,23 +1442,23 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.4.2", + "http", ] [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http 1.4.2", + "http", "http-body", "pin-project-lite", ] @@ -1694,12 +1469,6 @@ 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 = "hybrid-array" version = "0.4.13" @@ -1720,8 +1489,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.15", - "http 1.4.2", + "h2", + "http", "http-body", "httparse", "itoa", @@ -1737,7 +1506,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.2", + "http", "hyper", "hyper-util", "rustls", @@ -1756,14 +1525,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.2", + "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2 0.6.5", "system-configuration", "tokio", "tower-service", @@ -1874,18 +1643,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "impl-more" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" - -[[package]] -name = "impl-more" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" - [[package]] name = "indexmap" version = "2.14.0" @@ -1958,7 +1715,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", "windows-link", ] @@ -1973,7 +1730,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.119", ] [[package]] @@ -1992,7 +1749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2050,12 +1807,6 @@ dependencies = [ "log", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.5.0" @@ -2089,7 +1840,7 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "libc", "plain", "redox_syscall 0.9.0", @@ -2123,23 +1874,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" -[[package]] -name = "local-channel" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" -dependencies = [ - "futures-core", - "futures-sink", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" - [[package]] name = "lock_api" version = "0.4.14" @@ -2186,9 +1920,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "mime" @@ -2214,12 +1948,11 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.61.2", ] @@ -2279,8 +2012,8 @@ dependencies = [ [[package]] name = "mtp" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-client", "mtp-codec", @@ -2288,50 +2021,52 @@ dependencies = [ "mtp-crypto", "mtp-files", "mtp-host", + "mtp-transport", "mtp-type-map", + "mtp-webserver", ] [[package]] name = "mtp-client" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.8.6", + "rand 0.10.2", "tokio", ] [[package]] name = "mtp-codec" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "base64", "byteorder", "mtp-common", "mtp-crypto", "mtp-type-map", - "rand 0.8.6", + "rand 0.10.2", ] [[package]] name = "mtp-common" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "quinn", "rustls", - "thiserror 2.0.18", + "thiserror 2.0.19", "wtransport", ] [[package]] name = "mtp-crypto" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "base64", "chacha20poly1305", @@ -2340,59 +2075,93 @@ dependencies = [ "hkdf 0.13.0", "ml-dsa", "mlkem-tls", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_core 0.10.1", + "rustls", "serde", "sha2 0.11.0", "thiserror 1.0.69", + "tokio", "zeroize", ] [[package]] name = "mtp-files" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-crypto", + "rand 0.10.2", "thiserror 1.0.69", + "zeroize", ] [[package]] name = "mtp-host" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.8.6", + "rand 0.8.7", "tokio", + "tracing", + "wtransport", ] [[package]] name = "mtp-transport" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ - "log", + "async-trait", "mtp-codec", "mtp-common", + "mtp-crypto", "rcgen", "rustls", "rustls-native-certs", + "sha2 0.11.0", "tokio", + "tracing", "wtransport", ] [[package]] name = "mtp-type-map" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "serde", "serde_yaml", ] +[[package]] +name = "mtp-webserver" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +dependencies = [ + "async-trait", + "bytes", + "h3", + "h3-quinn", + "h3-webtransport", + "http", + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-host", + "mtp-transport", + "quinn", + "rand 0.10.2", + "rustls", + "thiserror 2.0.19", + "tokio", + "tracing", +] + [[package]] name = "nom" version = "7.1.3" @@ -2424,7 +2193,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.6", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -2466,9 +2235,9 @@ dependencies = [ [[package]] name = "octets" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" +checksum = "866cb5af6f3aa3c1b44c3c2d79d22165fbb1b102e1b3fb499864bfe34736ec4b" [[package]] name = "oid-registry" @@ -2483,29 +2252,28 @@ dependencies = [ name = "omega" version = "0.1.0" dependencies = [ - "actix-web", "aes-gcm", "ansi_term", "anyhow", "base64", + "bytes", "dashmap", "dotenv", "hex", "hkdf 0.12.4", + "http", "json", "mtp", - "mtp-crypto", "once_cell", - "rand 0.8.6", + "rand 0.8.7", "rand_core 0.6.4", "reqwest", "rustls", - "rustls-pemfile", "sha2 0.10.9", "sqlx", "strum", "strum_macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "uuid", "x448", @@ -2613,7 +2381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.4.1", + "fastrand 2.5.0", "futures-io", ] @@ -2644,7 +2412,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.0", + "der 0.8.1", "spki 0.8.0", ] @@ -2703,15 +2471,21 @@ dependencies = [ [[package]] name = "polyval" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" dependencies = [ "cpubits", "cpufeatures 0.3.0", "universal-hash 0.6.1", ] +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + [[package]] name = "potential_utf" version = "0.1.5" @@ -2744,9 +2518,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2759,13 +2533,14 @@ checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", + "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.4", - "thiserror 2.0.18", + "socket2 0.6.5", + "thiserror 2.0.19", "tokio", "tracing", "web-time", @@ -2779,6 +2554,7 @@ checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "aws-lc-rs", "bytes", + "fastbloom", "getrandom 0.4.3", "lru-slab", "rand 0.10.2", @@ -2787,8 +2563,9 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "slab", - "thiserror 2.0.18", + "thiserror 2.0.19", "tinyvec", "tracing", "web-time", @@ -2803,16 +2580,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.4", + "socket2 0.6.5", "tracing", "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2831,9 +2608,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha", @@ -2912,7 +2689,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] [[package]] @@ -2921,44 +2698,9 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] -[[package]] -name = "regex" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - [[package]] name = "reqwest" version = "0.13.4" @@ -2969,8 +2711,8 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.15", - "http 1.4.2", + "h2", + "http", "http-body", "http-body-util", "hyper", @@ -3075,7 +2817,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -3084,9 +2826,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", @@ -3110,15 +2852,6 @@ dependencies = [ "security-framework", ] -[[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.15.0" @@ -3210,7 +2943,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3235,9 +2968,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -3245,22 +2978,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.1", ] [[package]] @@ -3303,26 +3036,15 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -3404,15 +3126,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -3424,6 +3146,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.12" @@ -3451,19 +3179,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3471,9 +3189,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -3495,7 +3213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.0", + "der 0.8.1", ] [[package]] @@ -3546,7 +3264,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "url", ] @@ -3561,7 +3279,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.119", ] [[package]] @@ -3585,7 +3303,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.119", "url", ] @@ -3597,7 +3315,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.13.0", + "bitflags 2.13.1", "byteorder", "bytes", "crc", @@ -3618,15 +3336,15 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.6", + "rand 0.8.7", "rsa", "serde", - "sha1 0.10.6", + "sha1", "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "whoami", ] @@ -3639,7 +3357,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.13.0", + "bitflags 2.13.1", "byteorder", "crc", "dotenvy", @@ -3656,14 +3374,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.6", + "rand 0.8.7", "serde", "serde_json", "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "whoami", ] @@ -3687,7 +3405,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "url", ] @@ -3724,7 +3442,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3735,9 +3453,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f" dependencies = [ "proc-macro2", "quote", @@ -3761,7 +3490,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3770,7 +3499,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3796,11 +3525,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -3811,18 +3540,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.1", ] [[package]] @@ -3867,9 +3596,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -3882,9 +3611,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" dependencies = [ "bytes", "libc", @@ -3892,20 +3621,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.4", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3952,10 +3681,10 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "futures-util", - "http 1.4.2", + "http", "http-body", "pin-project-lite", "tower", @@ -3996,7 +3725,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4047,18 +3776,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -4117,9 +3834,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.4" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -4128,9 +3845,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +checksum = "5dd4ec1eb1d240636e354a30110a1dfcb37047169a4d9bd6d9d3469df574b5c4" [[package]] name = "vcpkg" @@ -4232,7 +3949,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -4267,9 +3984,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -4524,8 +4241,8 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2 0.6.4", - "thiserror 2.0.18", + "socket2 0.6.5", + "thiserror 2.0.19", "time", "tokio", "tracing", @@ -4542,7 +4259,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror 2.0.18", + "thiserror 2.0.19", "url", ] @@ -4584,7 +4301,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", ] @@ -4617,28 +4334,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4658,7 +4375,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -4679,7 +4396,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4712,7 +4429,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4735,7 +4452,7 @@ dependencies = [ "memchr", "pbkdf2", "ppmd-rust", - "sha1 0.10.6", + "sha1", "time", "zeroize", "zopfli", @@ -4744,15 +4461,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index d3de686..e601d1a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,20 +5,21 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ - "host", "crypto", "files", + "web-server", ] } -actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" ansi_term = "0.12.1" anyhow = "1.0.101" base64 = "0.22.1" +bytes = "1" dashmap = "6.1.0" dotenv = "0.15.0" hex = "0.4.3" hkdf = "0.12.4" +http = "1" json = "0.12.4" once_cell = "1.21.3" rand = "0.8" @@ -30,7 +31,6 @@ rustls = { version = "0.23.37", default-features = false, features = [ "aws-lc-rs", "prefer-post-quantum", ] } -rustls-pemfile = "2.2.0" sha2 = "0.10.9" sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } strum = "0.27.2" @@ -40,6 +40,3 @@ uuid = { version = "1.19.0", features = ["v4"] } x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" -mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = [ - "pqc", -] } diff --git a/src/main.rs b/src/main.rs index 2abf3a0..9e35297 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; use dotenv::from_path; -use mtp::files::{load_keyring as load_keyring_file, save_keyring, save_public_key_bundle}; -use mtp_crypto::Keyring; +use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; +use mtp::crypto::Keyring; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; @@ -20,9 +20,9 @@ use std::path::Path; const KEYRING_PATH: &str = "./omega.mk"; static KEYRING: Lazy = Lazy::new(|| { - load_keyring_file(KEYRING_PATH).unwrap_or_else(|_| { + load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| { let kr = Keyring::generate(); - save_keyring(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); + save_keyring_raw(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) .expect("Failed to save generated public key bundle"); eprintln!("Generated new keyring at {}", KEYRING_PATH); @@ -48,18 +48,6 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); - let omikron_port: u16 = env::var("OMIKRON_PORT") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(9187); - - let omikron_handle = tokio::spawn(async move { - match omikron_connection::start(omikron_port).await { - Err(e) => log_err!(0, PrintType::General, "{:?}", e), - _ => {} - } - }); - log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { @@ -77,13 +65,13 @@ async fn main() { log!(" Users"); } - let api_port: u16 = env::var("API_PORT") + let port: u16 = env::var("PORT") .ok() .and_then(|s| s.parse().ok()) - .unwrap_or(9188); + .unwrap_or(443); tokio::select! { - result = server::server::start(api_port) => { + result = omikron_connection::start(port) => { if let Err(e) = result { log_err!(0, PrintType::General, "Server error: {:?}", e); } @@ -92,6 +80,4 @@ async fn main() { log!("Shutting down on signal..."); } } - - omikron_handle.abort(); } diff --git a/src/server/api.rs b/src/server/api.rs index c8aeeb9..6471398 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -4,24 +4,31 @@ use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; -use actix_web::HttpResponse; -use actix_web::http::{StatusCode, header}; use base64::Engine as _; +use bytes::Bytes; +use http::{Method, StatusCode}; use json::JsonValue; +use mtp::webserver::{Http3Request, Http3Response, RouteParams}; -pub async fn handle(path: &str, body_string: Option) -> HttpResponse { - if path == "OPTIONS" { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) - .insert_header(("Access-Control-Allow-Headers", "*")) - .finish(); +pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Response { + let method = request.method; + let path = request.uri.path().to_string(); + let body_string = request + .body + .map(|body| String::from_utf8_lossy(&body).to_string()); + + if method == Method::OPTIONS { + return response + .status(StatusCode::OK) + .header("access-control-allow-origin", "*") + .header("access-control-allow-methods", "GET, POST, OPTIONS") + .header("access-control-allow-headers", "*"); } - let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); + let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); - let _body: Option = if body_string.is_some() { - if let Ok(body_json) = json::parse(&body_string.unwrap()) { + let _body: Option = if let Some(ref bs) = body_string { + if let Ok(body_json) = json::parse(bs) { Some(body_json) } else { None @@ -39,20 +46,22 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { match std::fs::read(file_path) { Ok(file_bytes) => { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Content-Type", "application/zip")) - .insert_header(( - "Content-Disposition", + return response + .status(StatusCode::OK) + .header("access-control-allow-origin", "*") + .header("content-type", "application/zip") + .header( + "content-disposition", "attachment; filename=\"iota_frontend.zip\"", - )) - .body(file_bytes); + ) + .body(Bytes::from(file_bytes)); } Err(_) => { let mut res = JsonValue::new_object(); res["status"] = "error_not_found".into(); - return HttpResponse::NotFound() - .insert_header(("Access-Control-Allow-Origin", "*")) + return response + .status(StatusCode::NOT_FOUND) + .header("access-control-allow-origin", "*") .body(res.dump()); } } @@ -309,6 +318,23 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } } + // ================================================== + // DIRECT - SHORT LINK RESOLUTION + // ================================================== + ["direct", short @ ..] => { + let short_str = short.join("/"); + let short = short_str.replace("/", ""); + if let Ok(long) = crate::server::short_link::get_short_link(&short).await { + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", &long); + } else { + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", "https://tensamin.net"); + } + } + // ================================================== // DEFAULT // ================================================== @@ -318,11 +344,19 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) } }; - let body_bytes = body_text.into_bytes(); - HttpResponse::build(status) - .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) - .body(body_bytes) + response + .status(status) + .header("access-control-allow-origin", "*") + .header("access-control-allow-headers", "*") + .header("access-control-allow-methods", "GET, POST, OPTIONS") + .body(body_text) +} + +pub async fn handle_pattern( + request: Http3Request, + response: Http3Response, + _params: RouteParams, +) -> Http3Response { + handle(request, response).await } diff --git a/src/server/index.rs b/src/server/index.rs index 1d302b1..b121209 100644 --- a/src/server/index.rs +++ b/src/server/index.rs @@ -1,6 +1,7 @@ -use actix_web::{HttpResponse, Responder}; +use http::StatusCode; +use mtp::webserver::Http3Response; -pub async fn index_handler() -> impl Responder { +pub fn index_handler(response: Http3Response) -> Http3Response { let documentation = r#" Omega API Server @@ -11,7 +12,8 @@ Available Routes: All other routes will return this documentation. "#; - HttpResponse::Ok() - .content_type("text/plain; charset=utf-8") + response + .status(StatusCode::OK) + .header("content-type", "text/plain; charset=utf-8") .body(documentation) } diff --git a/src/server/server.rs b/src/server/server.rs index 3c7fc05..f88e267 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,69 +1,19 @@ -use crate::{ - log, - server::{api, index::index_handler, short_link::get_short_link}, - util::file_util::load_file_buf, -}; +use crate::server::{api, index::index_handler}; +use mtp::webserver::WebServerConfig; -use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; - -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use rustls_pemfile::{certs, pkcs8_private_keys}; - -pub async fn start(port: u16) -> anyhow::Result<()> { - let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; - - let mut key_reader = load_file_buf("certs", "server_key.pem")?; - - let cert_chain: Vec> = - certs(&mut cert_reader).collect::>()?; - - let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) - .map(|res| res.map(Into::into)) - .collect::>()?; - - let key = keys.remove(0); - - let mut config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key)?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); - let addr = format!("{}:{}", bind_addr, port); - log!(" Server on {}", addr); - - HttpServer::new(move || { - App::new() - .route("/api/{path:.*}", web::to(api_handler)) - .route("/direct/{path:.*}", web::to(direct_handler)) - .default_service(web::to(index_handler)) - }) - .bind_rustls_0_23(addr, config)? - .run() - .await?; - - Ok(()) -} -async fn direct_handler(req: HttpRequest) -> impl Responder { - let path = req.uri().path().to_string(); - let short = path.replace("/direct/", ""); - - if let Ok(long) = get_short_link(&short).await { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, long)) - .finish() - } else { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, "https://tensamin.net")) - .finish() - } -} - -async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { - let path = req.uri().path().to_string(); - let body_string = String::from_utf8_lossy(&body).to_string(); - - api::handle(&path, Some(body_string)).await +pub fn build_web_config() -> Result { + WebServerConfig::new() + .route( + "/", + |_request, response| async move { index_handler(response) }, + )? + .route("/api/download/iota_frontend", api::handle)? + .route("/api/get/omikron", api::handle)? + .route("/api/get/connections", api::handle)? + .route("/api/get/public_key", api::handle)? + .route_pattern("/api/get/omikron/{id}", api::handle_pattern)? + .route_pattern("/api/get/iota/{id}", api::handle_pattern)? + .route_pattern("/api/get/id/{username}", api::handle_pattern)? + .route_pattern("/api/get/user/{id}", api::handle_pattern)? + .route_pattern("/direct/{short}", api::handle_pattern) } diff --git a/src/sql/sql.rs b/src/sql/sql.rs index da6d1bc..91cd5ba 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -1,5 +1,5 @@ use crate::log; -use mtp_crypto::PublicKeyBundle; +use mtp::crypto::PublicKeyBundle; use once_cell::sync::Lazy; use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -202,7 +202,7 @@ pub async fn get_by_username( let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -271,7 +271,7 @@ pub async fn get_by_user_id( let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -607,7 +607,10 @@ pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result Result<(), sqlx::Error> { +pub async fn register_complete_iota( + id: i64, + public_key: PublicKeyBundle, +) -> Result<(), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 2da779d..6a5517e 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,6 +1,6 @@ use crate::{ load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, - server::short_link::add_short_link, + server::{self, short_link::add_short_link}, sql::{ connection_status::UserStatus, sql::{self, get_by_user_id, get_by_username, get_iota_by_id}, @@ -11,12 +11,10 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::host::{AuthenticationPolicy, Receiver, Sender}; -use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, - host::{Host, HostConfig, Policy, SendMode}, -}; -use mtp_crypto::PublicKeyBundle; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::host::{AuthenticationPolicy, HostConfig, Policy, SendMode}; +use mtp::webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}; +use mtp::crypto::PublicKeyBundle; use std::net::{IpAddr, Ipv4Addr}; use std::{ sync::Arc, @@ -26,17 +24,10 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; -// ============================================================================ -// Configuration -// ============================================================================ const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); -// ============================================================================ -// Error Types -// ============================================================================ - #[derive(Debug, thiserror::Error)] pub enum OmikronError { #[error("Not connected")] @@ -55,22 +46,14 @@ pub enum OmikronError { pub type OmikronResult = Result; -// ============================================================================ -// Waiting Task System (Preserved from original) -// ============================================================================ - pub struct WaitingTask { pub task: Box, CommunicationValue) -> bool + Send + Sync>, pub inserted_at: Instant, } -// ============================================================================ -// Omikron Connection (mtp/QUIC-based) -// ============================================================================ - pub struct OmikronConnection { id: u64, - sender: Mutex>, + sender: Mutex>, pub ping: RwLock, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, @@ -85,11 +68,7 @@ impl Drop for OmikronConnection { } impl OmikronConnection { - // ------------------------------------------------------------------------- - // Construction - // ------------------------------------------------------------------------- - - pub fn new(sender: Sender, id: u64) -> Arc { + pub fn new(sender: WebMtpSender, id: u64) -> Arc { let conn = Arc::new(Self { id, sender: Mutex::new(Some(sender)), @@ -101,18 +80,13 @@ impl OmikronConnection { conn } - // ------------------------------------------------------------------------- - // Main Handler Loop - // ------------------------------------------------------------------------- - - pub async fn handle(self: Arc, receiver: &mut Receiver) { + pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, PrintType::Omega, "Omikron connection started" ); - // Start cleanup task let cleanup_conn = self.clone(); let cleanup_handle = tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); @@ -142,10 +116,6 @@ impl OmikronConnection { ); } - // ------------------------------------------------------------------------- - // Message Processing - // ------------------------------------------------------------------------- - async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_in!(PrintType::Omikron, &cv); @@ -153,18 +123,15 @@ impl OmikronConnection { let msg_id = cv.get_id(); - // Check waiting tasks first (response to previous request) if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { let _ = (task.task)(self.clone(), cv); return Ok(()); } - // Handle ping regardless of message type if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } - // Authentication is completed by the mtp host before this connection exists. let omikron_id = self.id as i64; self.clone().handle_authenticated(cv, omikron_id).await } @@ -176,10 +143,8 @@ impl OmikronConnection { ) -> OmikronResult<()> { let comm_type = cv.get_comm_type_enum(); match comm_type { - // Link shortening Some(CommunicationType::ShortenLink) => self.handle_shorten_link(cv).await, - // Online status tracking Some(CommunicationType::UserConnected) => { self.handle_user_connected(cv, omikron_id).await; Ok(()) @@ -234,10 +199,6 @@ impl OmikronConnection { } } - // ------------------------------------------------------------------------- - // Specific Handlers (ported from original WebSocket implementation) - // ------------------------------------------------------------------------- - async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let link = cv .get_data(DataType::Link) @@ -348,7 +309,6 @@ impl OmikronConnection { } async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by user_id first if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok(user_data) = get_by_user_id(user_id as i64).await { let response = self @@ -359,7 +319,6 @@ impl OmikronConnection { } } - // Try by username if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok(user_data) = get_by_username(username).await { let response = self @@ -370,7 +329,6 @@ impl OmikronConnection { } } - // Not found let response = CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); self.send(&response).await @@ -421,11 +379,9 @@ impl OmikronConnection { ) .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); - // Display name (fallback to username) let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); response = response.add_typed_default(DataType::Display, DataValue::Str(display_name)); - // Optional fields if let Some(s) = status.filter(|s| !s.is_empty()) { response = response.add_typed_default(DataType::Status, DataValue::Str(s)); } @@ -437,7 +393,6 @@ impl OmikronConnection { response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); } - // Online status let user_status = user_online_tracker::get_user_status(id); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); @@ -477,7 +432,6 @@ impl OmikronConnection { } async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by iota_id if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { let response = self @@ -488,7 +442,6 @@ impl OmikronConnection { } } - // Try by user_id if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(user_id as i64).await @@ -509,7 +462,6 @@ impl OmikronConnection { } } - // Try by username if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_username(username).await @@ -594,7 +546,6 @@ impl OmikronConnection { if let Some(public_key) = public_key { if let Some(iota_id) = iota_id_opt { - // Register existing IOTA match sql::register_complete_iota(iota_id, public_key).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::Success) @@ -609,7 +560,6 @@ impl OmikronConnection { } } } else { - // Create new IOTA match sql::create_new_iota(public_key).await { Ok(new_iota_id) => { let response = @@ -681,7 +631,6 @@ impl OmikronConnection { let mut success = true; let mut error_message = String::new(); - // Process each field if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Err(e) = sql::change_username(user_id, username.to_string()).await { success = false; @@ -747,7 +696,7 @@ impl OmikronConnection { ) { match sql::get_by_user_id(user_id).await { Ok(user) => { - let current_token = user.11; // reset_token field + let current_token = user.11; if current_token == reset_token { let mut success = true; let mut error_message = String::new(); @@ -889,7 +838,6 @@ impl OmikronConnection { .with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with other Omikron clients let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) .add_typed_default( @@ -926,7 +874,6 @@ impl OmikronConnection { CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with other Omikron clients let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) .add_typed_default( @@ -985,10 +932,6 @@ impl OmikronConnection { self.send(&response).await } - // ------------------------------------------------------------------------- - // Utilities - // ------------------------------------------------------------------------- - async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_out!(PrintType::Omikron, cv); @@ -1060,9 +1003,10 @@ pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option Result<(), Box> { let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); + let web_config = server::server::build_web_config()?; + let host_config = HostConfig::new( IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, @@ -1072,6 +1016,7 @@ pub async fn start(port: u16) -> Result<(), Box> { .with_policy(Policy { send_mode: SendMode::SingleStreamPerMessage, max_message_size: 1_000_000_000, + handshake_max_message_size: 1_000_000, close_frame_len: u32::MAX, application_close_code: 0, open_stream_timeout: Duration::from_millis(2_000), @@ -1081,9 +1026,11 @@ pub async fn start(port: u16) -> Result<(), Box> { keep_alive_interval: Some(Duration::from_secs(6)), max_idle_timeout: Some(Duration::from_secs(30)), force_close_delay: Duration::from_millis(300), - max_transient_recv_errors: 20, - transient_recv_backoff: Duration::from_millis(100), receiver_queue_capacity: 1000, + max_concurrent_stream_tasks: 10, + persistent_stream_max_retries: 5, + persistent_stream_retry_backoff: Duration::from_secs(5), + max_frames_per_stream: None, }) .with_authentication( load_keyring(), @@ -1092,17 +1039,14 @@ pub async fn start(port: u16) -> Result<(), Box> { ) .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); - let mut host: Host = Host::new(host_config).await?; - log!("OmikronServer listening on port {}", port); + let mut server = MTPWebServer::new(host_config, web_config).await?; + log!("OmegaServer listening on port {}", port); loop { - let mut conn = match host.accept().await { + let mut conn = match server.accept().await { Ok(Some(conn)) => conn, Ok(None) => break, Err(e) => { - // A single omikron's failed/aborted handshake (bad auth, a - // probe, a mid-handshake disconnect) must not take down the - // whole listener - only that connection attempt is lost. log_err!(0, PrintType::Omega, "Rejected omikron connection: {}", e); continue; } diff --git a/type-maps.yaml b/type-maps.yaml index 3659921..ce3d5e6 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -170,7 +170,7 @@ type_maps: CallState: 49 ScreenShare: 50 PrivateKeyHash: 51 - Accepted: 52 + # Accepted: 52 now part of default MTP AcceptedProfiles: 53 DeniedProfiles: 54 Content: 55 From b2f3ed12f3eecb87335d8932717531602c4794c6 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:28:13 +0200 Subject: [PATCH 180/220] [Upd] mtp update --- Cargo.lock | 1005 ++++++++++----------------- Cargo.toml | 9 +- src/main.rs | 28 +- src/server/api.rs | 88 ++- src/server/index.rs | 10 +- src/server/server.rs | 84 +-- src/sql/sql.rs | 11 +- src/transport/omikron_connection.rs | 94 +-- type-maps.yaml | 2 +- 9 files changed, 482 insertions(+), 849 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6dfdfc9..4ce5e8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,210 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "actix-codec" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" -dependencies = [ - "bitflags 2.13.0", - "bytes", - "futures-core", - "futures-sink", - "memchr", - "pin-project-lite", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-http" -version = "3.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e2faa3e7418ed780cca54829d32782a4008a077230f67457caa063415e99c2" -dependencies = [ - "actix-codec", - "actix-rt", - "actix-service", - "actix-tls", - "actix-utils", - "base64", - "bitflags 2.13.0", - "brotli", - "bytes", - "bytestring", - "derive_more", - "encoding_rs", - "flate2", - "foldhash 0.2.0", - "futures-core", - "h2 0.3.27", - "http 0.2.12", - "httparse", - "httpdate", - "itoa", - "language-tags", - "local-channel", - "mime", - "percent-encoding", - "pin-project-lite", - "rand 0.10.2", - "sha1 0.11.0", - "smallvec", - "tokio", - "tokio-util", - "tracing", - "zstd", -] - -[[package]] -name = "actix-macros" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" -dependencies = [ - "quote", - "syn", -] - -[[package]] -name = "actix-router" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f8c75c51892f18d9c46150c5ac7beb81c95f78c8b83a634d49f4ca32551fe7" -dependencies = [ - "bytestring", - "cfg-if", - "http 0.2.12", - "regex", - "regex-lite", - "serde", - "tracing", -] - -[[package]] -name = "actix-rt" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92589714878ca59a7626ea19734f0e07a6a875197eec751bb5d3f99e64998c63" -dependencies = [ - "futures-core", - "tokio", -] - -[[package]] -name = "actix-server" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65064ea4a457eaf07f2fba30b4c695bf43b721790e9530d26cb6f9019ff7502" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "futures-util", - "mio", - "socket2 0.5.10", - "tokio", - "tracing", -] - -[[package]] -name = "actix-service" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e46f36bf0e5af44bdc4bdb36fbbd421aa98c79a9bce724e1edeb3894e10dc7f" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "actix-tls" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6176099de3f58fbddac916a7f8c6db297e021d706e7a6b99947785fee14abe9f" -dependencies = [ - "actix-rt", - "actix-service", - "actix-utils", - "futures-core", - "impl-more 0.1.9", - "pin-project-lite", - "rustls-pki-types", - "tokio", - "tokio-rustls", - "tokio-util", - "tracing", -] - -[[package]] -name = "actix-utils" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" -dependencies = [ - "local-waker", - "pin-project-lite", -] - -[[package]] -name = "actix-web" -version = "4.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df09e2d9239703dd64056359c920c7f3fba6535ec61a0059e0f44e095ffe02b4" -dependencies = [ - "actix-codec", - "actix-http", - "actix-macros", - "actix-router", - "actix-rt", - "actix-server", - "actix-service", - "actix-tls", - "actix-utils", - "actix-web-codegen", - "bytes", - "bytestring", - "cfg-if", - "cookie", - "derive_more", - "encoding_rs", - "foldhash 0.2.0", - "futures-core", - "futures-util", - "impl-more 0.3.1", - "itoa", - "language-tags", - "log", - "mime", - "once_cell", - "pin-project-lite", - "regex", - "regex-lite", - "serde", - "serde_json", - "serde_urlencoded", - "smallvec", - "socket2 0.6.4", - "time", - "tracing", - "url", -] - -[[package]] -name = "actix-web-codegen" -version = "4.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" -dependencies = [ - "actix-router", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "adler2" version = "2.0.1" @@ -268,30 +64,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" -dependencies = [ - "alloc-no-stdlib", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -309,9 +81,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.103" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arbitrary" @@ -334,7 +106,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", ] @@ -346,7 +118,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -358,7 +130,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -392,7 +164,7 @@ checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" dependencies = [ "async-task", "concurrent-queue", - "fastrand 2.4.1", + "fastrand 2.5.0", "futures-lite 2.6.1", "pin-project-lite", "slab", @@ -503,6 +275,17 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-trait" +version = "0.1.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.1", +] + [[package]] name = "atoi" version = "2.0.0" @@ -526,9 +309,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.1" +version = "1.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad" +checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -537,9 +320,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.42.0" +version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444" +checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" dependencies = [ "cc", "cmake", @@ -577,9 +360,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.13.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" dependencies = [ "serde_core", ] @@ -615,27 +398,6 @@ dependencies = [ "piper", ] -[[package]] -name = "brotli" -version = "8.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - [[package]] name = "bumpalo" version = "3.20.3" @@ -650,18 +412,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" - -[[package]] -name = "bytestring" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" -dependencies = [ - "bytes", -] +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "bzip2" @@ -674,9 +427,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.66" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d6cac793997bd970000024b2934968efe83b382de4fdcf4fcb46b6ee4ad996" +checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" dependencies = [ "find-msvc-tools", "jobserver", @@ -692,9 +445,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" [[package]] name = "chacha20" @@ -805,26 +558,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" -[[package]] -name = "convert_case" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "cookie" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -978,7 +711,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1020,9 +753,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid 0.10.2", "zeroize", @@ -1056,30 +789,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "derive_more" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn", - "unicode-xid", + "syn 2.0.119", ] [[package]] @@ -1114,7 +824,7 @@ checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -1242,6 +952,18 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastbloom" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" +dependencies = [ + "foldhash 0.2.0", + "libm", + "portable-atomic", + "siphasher", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1253,9 +975,9 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fiat-crypto" @@ -1281,7 +1003,6 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ - "crc32fast", "miniz_oxide", "zlib-rs", ] @@ -1331,10 +1052,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] -name = "futures-channel" -version = "0.3.32" +name = "futures" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" dependencies = [ "futures-core", "futures-sink", @@ -1342,15 +1078,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" [[package]] name = "futures-executor" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" dependencies = [ "futures-core", "futures-task", @@ -1370,9 +1106,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" [[package]] name = "futures-lite" @@ -1395,7 +1131,7 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" dependencies = [ - "fastrand 2.4.1", + "fastrand 2.5.0", "futures-core", "futures-io", "parking", @@ -1403,25 +1139,38 @@ dependencies = [ ] [[package]] -name = "futures-sink" -version = "0.3.32" +name = "futures-macro" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" +checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" [[package]] name = "futures-task" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" [[package]] name = "futures-util" -version = "0.3.32" +version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" dependencies = [ + "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1499,25 +1248,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "h2" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "h2" version = "0.4.15" @@ -1529,7 +1259,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.2", + "http", "indexmap", "slab", "tokio", @@ -1537,6 +1267,62 @@ dependencies = [ "tracing", ] +[[package]] +name = "h3" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" +dependencies = [ + "bytes", + "fastrand 2.5.0", + "futures-util", + "http", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "h3-datagram" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d2c9f77921668673721ae40f17c729fc48b9e38a663858097cea547484fdf0f" +dependencies = [ + "bytes", + "h3", + "pin-project-lite", +] + +[[package]] +name = "h3-quinn" +version = "0.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2e732c8d91a74731663ac8479ab505042fbf547b9a207213ab7fbcbfc4f8b4" +dependencies = [ + "bytes", + "futures", + "h3", + "h3-datagram", + "quinn", + "tokio", + "tokio-util", +] + +[[package]] +name = "h3-webtransport" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d91a50fd582a5d67b1f756fba3cd9c66367ff4f23e1017c882f664d63b350a7" +dependencies = [ + "bytes", + "futures-util", + "h3", + "h3-datagram", + "http", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1644,17 +1430,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.2" @@ -1667,23 +1442,23 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", - "http 1.4.2", + "http", ] [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" dependencies = [ "bytes", "futures-core", - "http 1.4.2", + "http", "http-body", "pin-project-lite", ] @@ -1694,12 +1469,6 @@ 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 = "hybrid-array" version = "0.4.13" @@ -1720,8 +1489,8 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2 0.4.15", - "http 1.4.2", + "h2", + "http", "http-body", "httparse", "itoa", @@ -1737,7 +1506,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http 1.4.2", + "http", "hyper", "hyper-util", "rustls", @@ -1756,14 +1525,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.2", + "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.4", + "socket2 0.6.5", "system-configuration", "tokio", "tower-service", @@ -1874,18 +1643,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "impl-more" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a5a9a0ff0086c7a148acb942baaabeadf9504d10400b5a05645853729b9cd2" - -[[package]] -name = "impl-more" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a84fd5aa25fae5c0f4a33d9cac2ca017fc622cbd089be2229993514990f870" - [[package]] name = "indexmap" version = "2.14.0" @@ -1958,7 +1715,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.18", + "thiserror 2.0.19", "walkdir", "windows-link", ] @@ -1973,7 +1730,7 @@ dependencies = [ "quote", "rustc_version", "simd_cesu8", - "syn", + "syn 2.0.119", ] [[package]] @@ -1992,7 +1749,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -2050,12 +1807,6 @@ dependencies = [ "log", ] -[[package]] -name = "language-tags" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" - [[package]] name = "lazy_static" version = "1.5.0" @@ -2089,7 +1840,7 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "libc", "plain", "redox_syscall 0.9.0", @@ -2123,23 +1874,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" -[[package]] -name = "local-channel" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" -dependencies = [ - "futures-core", - "futures-sink", - "local-waker", -] - -[[package]] -name = "local-waker" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" - [[package]] name = "lock_api" version = "0.4.14" @@ -2186,9 +1920,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.8.2" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "mime" @@ -2214,12 +1948,11 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", - "log", "wasi", "windows-sys 0.61.2", ] @@ -2279,8 +2012,8 @@ dependencies = [ [[package]] name = "mtp" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-client", "mtp-codec", @@ -2288,50 +2021,52 @@ dependencies = [ "mtp-crypto", "mtp-files", "mtp-host", + "mtp-transport", "mtp-type-map", + "mtp-webserver", ] [[package]] name = "mtp-client" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.8.6", + "rand 0.10.2", "tokio", ] [[package]] name = "mtp-codec" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "base64", "byteorder", "mtp-common", "mtp-crypto", "mtp-type-map", - "rand 0.8.6", + "rand 0.10.2", ] [[package]] name = "mtp-common" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "quinn", "rustls", - "thiserror 2.0.18", + "thiserror 2.0.19", "wtransport", ] [[package]] name = "mtp-crypto" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "base64", "chacha20poly1305", @@ -2340,59 +2075,93 @@ dependencies = [ "hkdf 0.13.0", "ml-dsa", "mlkem-tls", - "rand_core 0.6.4", + "rand 0.10.2", + "rand_core 0.10.1", + "rustls", "serde", "sha2 0.11.0", "thiserror 1.0.69", + "tokio", "zeroize", ] [[package]] name = "mtp-files" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-crypto", + "rand 0.10.2", "thiserror 1.0.69", + "zeroize", ] [[package]] name = "mtp-host" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.8.6", + "rand 0.8.7", "tokio", + "tracing", + "wtransport", ] [[package]] name = "mtp-transport" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ - "log", + "async-trait", "mtp-codec", "mtp-common", + "mtp-crypto", "rcgen", "rustls", "rustls-native-certs", + "sha2 0.11.0", "tokio", + "tracing", "wtransport", ] [[package]] name = "mtp-type-map" -version = "0.1.0" -source = "git+https://git.methanium.net/methanium/mtp#2126a142f44634ccfdcaa297633fdb63b1d9b46d" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" dependencies = [ "serde", "serde_yaml", ] +[[package]] +name = "mtp-webserver" +version = "0.2.0" +source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +dependencies = [ + "async-trait", + "bytes", + "h3", + "h3-quinn", + "h3-webtransport", + "http", + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-host", + "mtp-transport", + "quinn", + "rand 0.10.2", + "rustls", + "thiserror 2.0.19", + "tokio", + "tracing", +] + [[package]] name = "nom" version = "7.1.3" @@ -2424,7 +2193,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand 0.8.6", + "rand 0.8.7", "smallvec", "zeroize", ] @@ -2466,9 +2235,9 @@ dependencies = [ [[package]] name = "octets" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" +checksum = "866cb5af6f3aa3c1b44c3c2d79d22165fbb1b102e1b3fb499864bfe34736ec4b" [[package]] name = "oid-registry" @@ -2483,29 +2252,28 @@ dependencies = [ name = "omega" version = "0.1.0" dependencies = [ - "actix-web", "aes-gcm", "ansi_term", "anyhow", "base64", + "bytes", "dashmap", "dotenv", "hex", "hkdf 0.12.4", + "http", "json", "mtp", - "mtp-crypto", "once_cell", - "rand 0.8.6", + "rand 0.8.7", "rand_core 0.6.4", "reqwest", "rustls", - "rustls-pemfile", "sha2 0.10.9", "sqlx", "strum", "strum_macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "tokio", "uuid", "x448", @@ -2613,7 +2381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" dependencies = [ "atomic-waker", - "fastrand 2.4.1", + "fastrand 2.5.0", "futures-io", ] @@ -2644,7 +2412,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.0", + "der 0.8.1", "spki 0.8.0", ] @@ -2703,15 +2471,21 @@ dependencies = [ [[package]] name = "polyval" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" dependencies = [ "cpubits", "cpufeatures 0.3.0", "universal-hash 0.6.1", ] +[[package]] +name = "portable-atomic" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" + [[package]] name = "potential_utf" version = "0.1.5" @@ -2744,9 +2518,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.106" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] @@ -2759,13 +2533,14 @@ checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", + "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.4", - "thiserror 2.0.18", + "socket2 0.6.5", + "thiserror 2.0.19", "tokio", "tracing", "web-time", @@ -2779,6 +2554,7 @@ checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" dependencies = [ "aws-lc-rs", "bytes", + "fastbloom", "getrandom 0.4.3", "lru-slab", "rand 0.10.2", @@ -2787,8 +2563,9 @@ dependencies = [ "rustc-hash", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "slab", - "thiserror 2.0.18", + "thiserror 2.0.19", "tinyvec", "tracing", "web-time", @@ -2803,16 +2580,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.4", + "socket2 0.6.5", "tracing", "windows-sys 0.61.2", ] [[package]] name = "quote" -version = "1.0.46" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" dependencies = [ "proc-macro2", ] @@ -2831,9 +2608,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ "libc", "rand_chacha", @@ -2912,7 +2689,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] [[package]] @@ -2921,44 +2698,9 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", ] -[[package]] -name = "regex" -version = "1.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-lite" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - [[package]] name = "reqwest" version = "0.13.4" @@ -2969,8 +2711,8 @@ dependencies = [ "bytes", "encoding_rs", "futures-core", - "h2 0.4.15", - "http 1.4.2", + "h2", + "http", "http-body", "http-body-util", "hyper", @@ -3075,7 +2817,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys 0.12.1", @@ -3084,9 +2826,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.41" +version = "0.23.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" dependencies = [ "aws-lc-rs", "log", @@ -3110,15 +2852,6 @@ dependencies = [ "security-framework", ] -[[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.15.0" @@ -3210,7 +2943,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -3235,9 +2968,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -3245,22 +2978,22 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.1", ] [[package]] @@ -3303,26 +3036,15 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" dependencies = [ "cfg-if", "cpufeatures 0.2.17", "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -3404,15 +3126,15 @@ dependencies = [ [[package]] name = "simd-adler32" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" [[package]] name = "simd_cesu8" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ "rustc_version", "simdutf8", @@ -3424,6 +3146,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + [[package]] name = "slab" version = "0.4.12" @@ -3451,19 +3179,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.10" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "socket2" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", @@ -3471,9 +3189,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.8" +version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" dependencies = [ "lock_api", ] @@ -3495,7 +3213,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.0", + "der 0.8.1", ] [[package]] @@ -3546,7 +3264,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "url", ] @@ -3561,7 +3279,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.119", ] [[package]] @@ -3585,7 +3303,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.119", "url", ] @@ -3597,7 +3315,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.13.0", + "bitflags 2.13.1", "byteorder", "bytes", "crc", @@ -3618,15 +3336,15 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand 0.8.6", + "rand 0.8.7", "rsa", "serde", - "sha1 0.10.6", + "sha1", "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "whoami", ] @@ -3639,7 +3357,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.13.0", + "bitflags 2.13.1", "byteorder", "crc", "dotenvy", @@ -3656,14 +3374,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand 0.8.6", + "rand 0.8.7", "serde", "serde_json", "sha2 0.10.9", "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "whoami", ] @@ -3687,7 +3405,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.18", + "thiserror 2.0.19", "tracing", "url", ] @@ -3724,7 +3442,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3735,9 +3453,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.118" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f" dependencies = [ "proc-macro2", "quote", @@ -3761,7 +3490,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3770,7 +3499,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3796,11 +3525,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.18", + "thiserror-impl 2.0.19", ] [[package]] @@ -3811,18 +3540,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] name = "thiserror-impl" -version = "2.0.18" +version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.1", ] [[package]] @@ -3867,9 +3596,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" dependencies = [ "tinyvec_macros", ] @@ -3882,9 +3611,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" dependencies = [ "bytes", "libc", @@ -3892,20 +3621,20 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.4", + "socket2 0.6.5", "tokio-macros", "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -3952,10 +3681,10 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.0", + "bitflags 2.13.1", "bytes", "futures-util", - "http 1.4.2", + "http", "http-body", "pin-project-lite", "tower", @@ -3996,7 +3725,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4047,18 +3776,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" -[[package]] -name = "unicode-segmentation" -version = "1.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -4117,9 +3834,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.23.4" +version = "1.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -4128,9 +3845,9 @@ dependencies = [ [[package]] name = "value-bag" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" +checksum = "5dd4ec1eb1d240636e354a30110a1dfcb37047169a4d9bd6d9d3469df574b5c4" [[package]] name = "vcpkg" @@ -4232,7 +3949,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.119", "wasm-bindgen-shared", ] @@ -4267,9 +3984,9 @@ dependencies = [ [[package]] name = "webpki-root-certs" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] @@ -4524,8 +4241,8 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2 0.6.4", - "thiserror 2.0.18", + "socket2 0.6.5", + "thiserror 2.0.19", "time", "tokio", "tracing", @@ -4542,7 +4259,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror 2.0.18", + "thiserror 2.0.19", "url", ] @@ -4584,7 +4301,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.18", + "thiserror 2.0.19", "time", ] @@ -4617,28 +4334,28 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75726053136156d419e285b9b7eddaaea9e3fea6ce32eed44a89901f0bd98de1" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.53" +version = "0.8.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4714fd92cf900833d49538023a9b3915155210801d1c1169eba513b2addefd71" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4658,7 +4375,7 @@ checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", "synstructure", ] @@ -4679,7 +4396,7 @@ checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4712,7 +4429,7 @@ checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.119", ] [[package]] @@ -4735,7 +4452,7 @@ dependencies = [ "memchr", "pbkdf2", "ppmd-rust", - "sha1 0.10.6", + "sha1", "time", "zeroize", "zopfli", @@ -4744,15 +4461,15 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5431d5661c32445236631278f27946e444ddafe4684cac70b185272d4f9c52d5" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index d3de686..e601d1a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,20 +5,21 @@ edition = "2024" [dependencies] mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ - "host", "crypto", "files", + "web-server", ] } -actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" ansi_term = "0.12.1" anyhow = "1.0.101" base64 = "0.22.1" +bytes = "1" dashmap = "6.1.0" dotenv = "0.15.0" hex = "0.4.3" hkdf = "0.12.4" +http = "1" json = "0.12.4" once_cell = "1.21.3" rand = "0.8" @@ -30,7 +31,6 @@ rustls = { version = "0.23.37", default-features = false, features = [ "aws-lc-rs", "prefer-post-quantum", ] } -rustls-pemfile = "2.2.0" sha2 = "0.10.9" sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } strum = "0.27.2" @@ -40,6 +40,3 @@ uuid = { version = "1.19.0", features = ["v4"] } x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" -mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = [ - "pqc", -] } diff --git a/src/main.rs b/src/main.rs index 2abf3a0..9e35297 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; use dotenv::from_path; -use mtp::files::{load_keyring as load_keyring_file, save_keyring, save_public_key_bundle}; -use mtp_crypto::Keyring; +use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; +use mtp::crypto::Keyring; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; @@ -20,9 +20,9 @@ use std::path::Path; const KEYRING_PATH: &str = "./omega.mk"; static KEYRING: Lazy = Lazy::new(|| { - load_keyring_file(KEYRING_PATH).unwrap_or_else(|_| { + load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| { let kr = Keyring::generate(); - save_keyring(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); + save_keyring_raw(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) .expect("Failed to save generated public key bundle"); eprintln!("Generated new keyring at {}", KEYRING_PATH); @@ -48,18 +48,6 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); - let omikron_port: u16 = env::var("OMIKRON_PORT") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(9187); - - let omikron_handle = tokio::spawn(async move { - match omikron_connection::start(omikron_port).await { - Err(e) => log_err!(0, PrintType::General, "{:?}", e), - _ => {} - } - }); - log!("Started"); log!(" .env"); if let Err(e) = initialize_db().await { @@ -77,13 +65,13 @@ async fn main() { log!(" Users"); } - let api_port: u16 = env::var("API_PORT") + let port: u16 = env::var("PORT") .ok() .and_then(|s| s.parse().ok()) - .unwrap_or(9188); + .unwrap_or(443); tokio::select! { - result = server::server::start(api_port) => { + result = omikron_connection::start(port) => { if let Err(e) = result { log_err!(0, PrintType::General, "Server error: {:?}", e); } @@ -92,6 +80,4 @@ async fn main() { log!("Shutting down on signal..."); } } - - omikron_handle.abort(); } diff --git a/src/server/api.rs b/src/server/api.rs index c8aeeb9..6471398 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -4,24 +4,31 @@ use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; -use actix_web::HttpResponse; -use actix_web::http::{StatusCode, header}; use base64::Engine as _; +use bytes::Bytes; +use http::{Method, StatusCode}; use json::JsonValue; +use mtp::webserver::{Http3Request, Http3Response, RouteParams}; -pub async fn handle(path: &str, body_string: Option) -> HttpResponse { - if path == "OPTIONS" { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Access-Control-Allow-Methods", "GET, POST, OPTIONS")) - .insert_header(("Access-Control-Allow-Headers", "*")) - .finish(); +pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Response { + let method = request.method; + let path = request.uri.path().to_string(); + let body_string = request + .body + .map(|body| String::from_utf8_lossy(&body).to_string()); + + if method == Method::OPTIONS { + return response + .status(StatusCode::OK) + .header("access-control-allow-origin", "*") + .header("access-control-allow-methods", "GET, POST, OPTIONS") + .header("access-control-allow-headers", "*"); } - let path_parts: Vec<&str> = path.split("/").filter(|s| !s.is_empty()).collect(); + let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); - let _body: Option = if body_string.is_some() { - if let Ok(body_json) = json::parse(&body_string.unwrap()) { + let _body: Option = if let Some(ref bs) = body_string { + if let Ok(body_json) = json::parse(bs) { Some(body_json) } else { None @@ -39,20 +46,22 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { match std::fs::read(file_path) { Ok(file_bytes) => { - return HttpResponse::Ok() - .insert_header(("Access-Control-Allow-Origin", "*")) - .insert_header(("Content-Type", "application/zip")) - .insert_header(( - "Content-Disposition", + return response + .status(StatusCode::OK) + .header("access-control-allow-origin", "*") + .header("content-type", "application/zip") + .header( + "content-disposition", "attachment; filename=\"iota_frontend.zip\"", - )) - .body(file_bytes); + ) + .body(Bytes::from(file_bytes)); } Err(_) => { let mut res = JsonValue::new_object(); res["status"] = "error_not_found".into(); - return HttpResponse::NotFound() - .insert_header(("Access-Control-Allow-Origin", "*")) + return response + .status(StatusCode::NOT_FOUND) + .header("access-control-allow-origin", "*") .body(res.dump()); } } @@ -309,6 +318,23 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { } } + // ================================================== + // DIRECT - SHORT LINK RESOLUTION + // ================================================== + ["direct", short @ ..] => { + let short_str = short.join("/"); + let short = short_str.replace("/", ""); + if let Ok(long) = crate::server::short_link::get_short_link(&short).await { + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", &long); + } else { + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", "https://tensamin.net"); + } + } + // ================================================== // DEFAULT // ================================================== @@ -318,11 +344,19 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) } }; - let body_bytes = body_text.into_bytes(); - HttpResponse::build(status) - .insert_header((header::ACCESS_CONTROL_ALLOW_ORIGIN, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_HEADERS, "*")) - .insert_header((header::ACCESS_CONTROL_ALLOW_METHODS, "GET, POST, OPTIONS")) - .body(body_bytes) + response + .status(status) + .header("access-control-allow-origin", "*") + .header("access-control-allow-headers", "*") + .header("access-control-allow-methods", "GET, POST, OPTIONS") + .body(body_text) +} + +pub async fn handle_pattern( + request: Http3Request, + response: Http3Response, + _params: RouteParams, +) -> Http3Response { + handle(request, response).await } diff --git a/src/server/index.rs b/src/server/index.rs index 1d302b1..b121209 100644 --- a/src/server/index.rs +++ b/src/server/index.rs @@ -1,6 +1,7 @@ -use actix_web::{HttpResponse, Responder}; +use http::StatusCode; +use mtp::webserver::Http3Response; -pub async fn index_handler() -> impl Responder { +pub fn index_handler(response: Http3Response) -> Http3Response { let documentation = r#" Omega API Server @@ -11,7 +12,8 @@ Available Routes: All other routes will return this documentation. "#; - HttpResponse::Ok() - .content_type("text/plain; charset=utf-8") + response + .status(StatusCode::OK) + .header("content-type", "text/plain; charset=utf-8") .body(documentation) } diff --git a/src/server/server.rs b/src/server/server.rs index 3c7fc05..f88e267 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,69 +1,19 @@ -use crate::{ - log, - server::{api, index::index_handler, short_link::get_short_link}, - util::file_util::load_file_buf, -}; +use crate::server::{api, index::index_handler}; +use mtp::webserver::WebServerConfig; -use actix_web::{App, HttpRequest, HttpResponse, HttpServer, Responder, http::header, web}; - -use rustls::ServerConfig; -use rustls::pki_types::{CertificateDer, PrivateKeyDer}; -use rustls_pemfile::{certs, pkcs8_private_keys}; - -pub async fn start(port: u16) -> anyhow::Result<()> { - let mut cert_reader = load_file_buf("certs", "server_cert.pem")?; - - let mut key_reader = load_file_buf("certs", "server_key.pem")?; - - let cert_chain: Vec> = - certs(&mut cert_reader).collect::>()?; - - let mut keys: Vec> = pkcs8_private_keys(&mut key_reader) - .map(|res| res.map(Into::into)) - .collect::>()?; - - let key = keys.remove(0); - - let mut config = ServerConfig::builder() - .with_no_client_auth() - .with_single_cert(cert_chain, key)?; - - config.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; - - let bind_addr = std::env::var("BIND_ADDRESS").unwrap_or_else(|_| "0.0.0.0".to_string()); - let addr = format!("{}:{}", bind_addr, port); - log!(" Server on {}", addr); - - HttpServer::new(move || { - App::new() - .route("/api/{path:.*}", web::to(api_handler)) - .route("/direct/{path:.*}", web::to(direct_handler)) - .default_service(web::to(index_handler)) - }) - .bind_rustls_0_23(addr, config)? - .run() - .await?; - - Ok(()) -} -async fn direct_handler(req: HttpRequest) -> impl Responder { - let path = req.uri().path().to_string(); - let short = path.replace("/direct/", ""); - - if let Ok(long) = get_short_link(&short).await { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, long)) - .finish() - } else { - HttpResponse::TemporaryRedirect() - .append_header((header::LOCATION, "https://tensamin.net")) - .finish() - } -} - -async fn api_handler(req: HttpRequest, body: web::Bytes) -> HttpResponse { - let path = req.uri().path().to_string(); - let body_string = String::from_utf8_lossy(&body).to_string(); - - api::handle(&path, Some(body_string)).await +pub fn build_web_config() -> Result { + WebServerConfig::new() + .route( + "/", + |_request, response| async move { index_handler(response) }, + )? + .route("/api/download/iota_frontend", api::handle)? + .route("/api/get/omikron", api::handle)? + .route("/api/get/connections", api::handle)? + .route("/api/get/public_key", api::handle)? + .route_pattern("/api/get/omikron/{id}", api::handle_pattern)? + .route_pattern("/api/get/iota/{id}", api::handle_pattern)? + .route_pattern("/api/get/id/{username}", api::handle_pattern)? + .route_pattern("/api/get/user/{id}", api::handle_pattern)? + .route_pattern("/direct/{short}", api::handle_pattern) } diff --git a/src/sql/sql.rs b/src/sql/sql.rs index da6d1bc..91cd5ba 100644 --- a/src/sql/sql.rs +++ b/src/sql/sql.rs @@ -1,5 +1,5 @@ use crate::log; -use mtp_crypto::PublicKeyBundle; +use mtp::crypto::PublicKeyBundle; use once_cell::sync::Lazy; use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; use std::sync::atomic::{AtomicU64, Ordering}; @@ -202,7 +202,7 @@ pub async fn get_by_username( let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -271,7 +271,7 @@ pub async fn get_by_user_id( let sub_level: i32 = row.get("sub_level"); let sub_end: i64 = row.get("sub_end"); let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; + .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; let private_key_hash: Vec = row.get("private_key_hash"); let token: Vec = row.get("token"); @@ -607,7 +607,10 @@ pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result Result<(), sqlx::Error> { +pub async fn register_complete_iota( + id: i64, + public_key: PublicKeyBundle, +) -> Result<(), sqlx::Error> { let pool = { let db_lock = SQL_DB.read().await; db_lock diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 2da779d..6a5517e 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,6 +1,6 @@ use crate::{ load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, - server::short_link::add_short_link, + server::{self, short_link::add_short_link}, sql::{ connection_status::UserStatus, sql::{self, get_by_user_id, get_by_username, get_iota_by_id}, @@ -11,12 +11,10 @@ use crate::{ }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::host::{AuthenticationPolicy, Receiver, Sender}; -use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, - host::{Host, HostConfig, Policy, SendMode}, -}; -use mtp_crypto::PublicKeyBundle; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::host::{AuthenticationPolicy, HostConfig, Policy, SendMode}; +use mtp::webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}; +use mtp::crypto::PublicKeyBundle; use std::net::{IpAddr, Ipv4Addr}; use std::{ sync::Arc, @@ -26,17 +24,10 @@ use tokio::{ sync::{Mutex, RwLock}, time::interval, }; -// ============================================================================ -// Configuration -// ============================================================================ const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); -// ============================================================================ -// Error Types -// ============================================================================ - #[derive(Debug, thiserror::Error)] pub enum OmikronError { #[error("Not connected")] @@ -55,22 +46,14 @@ pub enum OmikronError { pub type OmikronResult = Result; -// ============================================================================ -// Waiting Task System (Preserved from original) -// ============================================================================ - pub struct WaitingTask { pub task: Box, CommunicationValue) -> bool + Send + Sync>, pub inserted_at: Instant, } -// ============================================================================ -// Omikron Connection (mtp/QUIC-based) -// ============================================================================ - pub struct OmikronConnection { id: u64, - sender: Mutex>, + sender: Mutex>, pub ping: RwLock, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, @@ -85,11 +68,7 @@ impl Drop for OmikronConnection { } impl OmikronConnection { - // ------------------------------------------------------------------------- - // Construction - // ------------------------------------------------------------------------- - - pub fn new(sender: Sender, id: u64) -> Arc { + pub fn new(sender: WebMtpSender, id: u64) -> Arc { let conn = Arc::new(Self { id, sender: Mutex::new(Some(sender)), @@ -101,18 +80,13 @@ impl OmikronConnection { conn } - // ------------------------------------------------------------------------- - // Main Handler Loop - // ------------------------------------------------------------------------- - - pub async fn handle(self: Arc, receiver: &mut Receiver) { + pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, PrintType::Omega, "Omikron connection started" ); - // Start cleanup task let cleanup_conn = self.clone(); let cleanup_handle = tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); @@ -142,10 +116,6 @@ impl OmikronConnection { ); } - // ------------------------------------------------------------------------- - // Message Processing - // ------------------------------------------------------------------------- - async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_in!(PrintType::Omikron, &cv); @@ -153,18 +123,15 @@ impl OmikronConnection { let msg_id = cv.get_id(); - // Check waiting tasks first (response to previous request) if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { let _ = (task.task)(self.clone(), cv); return Ok(()); } - // Handle ping regardless of message type if cv.is_type(CommunicationType::Ping) { return self.handle_ping(cv).await; } - // Authentication is completed by the mtp host before this connection exists. let omikron_id = self.id as i64; self.clone().handle_authenticated(cv, omikron_id).await } @@ -176,10 +143,8 @@ impl OmikronConnection { ) -> OmikronResult<()> { let comm_type = cv.get_comm_type_enum(); match comm_type { - // Link shortening Some(CommunicationType::ShortenLink) => self.handle_shorten_link(cv).await, - // Online status tracking Some(CommunicationType::UserConnected) => { self.handle_user_connected(cv, omikron_id).await; Ok(()) @@ -234,10 +199,6 @@ impl OmikronConnection { } } - // ------------------------------------------------------------------------- - // Specific Handlers (ported from original WebSocket implementation) - // ------------------------------------------------------------------------- - async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { let link = cv .get_data(DataType::Link) @@ -348,7 +309,6 @@ impl OmikronConnection { } async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by user_id first if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok(user_data) = get_by_user_id(user_id as i64).await { let response = self @@ -359,7 +319,6 @@ impl OmikronConnection { } } - // Try by username if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok(user_data) = get_by_username(username).await { let response = self @@ -370,7 +329,6 @@ impl OmikronConnection { } } - // Not found let response = CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); self.send(&response).await @@ -421,11 +379,9 @@ impl OmikronConnection { ) .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); - // Display name (fallback to username) let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); response = response.add_typed_default(DataType::Display, DataValue::Str(display_name)); - // Optional fields if let Some(s) = status.filter(|s| !s.is_empty()) { response = response.add_typed_default(DataType::Status, DataValue::Str(s)); } @@ -437,7 +393,6 @@ impl OmikronConnection { response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); } - // Online status let user_status = user_online_tracker::get_user_status(id); let iota_connections = user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); @@ -477,7 +432,6 @@ impl OmikronConnection { } async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - // Try by iota_id if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { let response = self @@ -488,7 +442,6 @@ impl OmikronConnection { } } - // Try by user_id if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(user_id as i64).await @@ -509,7 +462,6 @@ impl OmikronConnection { } } - // Try by username if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_username(username).await @@ -594,7 +546,6 @@ impl OmikronConnection { if let Some(public_key) = public_key { if let Some(iota_id) = iota_id_opt { - // Register existing IOTA match sql::register_complete_iota(iota_id, public_key).await { Ok(_) => { let response = CommunicationValue::new(CommunicationType::Success) @@ -609,7 +560,6 @@ impl OmikronConnection { } } } else { - // Create new IOTA match sql::create_new_iota(public_key).await { Ok(new_iota_id) => { let response = @@ -681,7 +631,6 @@ impl OmikronConnection { let mut success = true; let mut error_message = String::new(); - // Process each field if let Some(username) = cv.get_data(DataType::Username).as_str() { if let Err(e) = sql::change_username(user_id, username.to_string()).await { success = false; @@ -747,7 +696,7 @@ impl OmikronConnection { ) { match sql::get_by_user_id(user_id).await { Ok(user) => { - let current_token = user.11; // reset_token field + let current_token = user.11; if current_token == reset_token { let mut success = true; let mut error_message = String::new(); @@ -889,7 +838,6 @@ impl OmikronConnection { .with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with other Omikron clients let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver_id as u64) .add_typed_default( @@ -926,7 +874,6 @@ impl OmikronConnection { CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); let _ = self.send(&response).await; - // Sync with other Omikron clients let push_cv = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver_id as u64) .add_typed_default( @@ -985,10 +932,6 @@ impl OmikronConnection { self.send(&response).await } - // ------------------------------------------------------------------------- - // Utilities - // ------------------------------------------------------------------------- - async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { log_cv_out!(PrintType::Omikron, cv); @@ -1060,9 +1003,10 @@ pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option Result<(), Box> { let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); + let web_config = server::server::build_web_config()?; + let host_config = HostConfig::new( IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, @@ -1072,6 +1016,7 @@ pub async fn start(port: u16) -> Result<(), Box> { .with_policy(Policy { send_mode: SendMode::SingleStreamPerMessage, max_message_size: 1_000_000_000, + handshake_max_message_size: 1_000_000, close_frame_len: u32::MAX, application_close_code: 0, open_stream_timeout: Duration::from_millis(2_000), @@ -1081,9 +1026,11 @@ pub async fn start(port: u16) -> Result<(), Box> { keep_alive_interval: Some(Duration::from_secs(6)), max_idle_timeout: Some(Duration::from_secs(30)), force_close_delay: Duration::from_millis(300), - max_transient_recv_errors: 20, - transient_recv_backoff: Duration::from_millis(100), receiver_queue_capacity: 1000, + max_concurrent_stream_tasks: 10, + persistent_stream_max_retries: 5, + persistent_stream_retry_backoff: Duration::from_secs(5), + max_frames_per_stream: None, }) .with_authentication( load_keyring(), @@ -1092,17 +1039,14 @@ pub async fn start(port: u16) -> Result<(), Box> { ) .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); - let mut host: Host = Host::new(host_config).await?; - log!("OmikronServer listening on port {}", port); + let mut server = MTPWebServer::new(host_config, web_config).await?; + log!("OmegaServer listening on port {}", port); loop { - let mut conn = match host.accept().await { + let mut conn = match server.accept().await { Ok(Some(conn)) => conn, Ok(None) => break, Err(e) => { - // A single omikron's failed/aborted handshake (bad auth, a - // probe, a mid-handshake disconnect) must not take down the - // whole listener - only that connection attempt is lost. log_err!(0, PrintType::Omega, "Rejected omikron connection: {}", e); continue; } diff --git a/type-maps.yaml b/type-maps.yaml index 3659921..ce3d5e6 100644 --- a/type-maps.yaml +++ b/type-maps.yaml @@ -170,7 +170,7 @@ type_maps: CallState: 49 ScreenShare: 50 PrivateKeyHash: 51 - Accepted: 52 + # Accepted: 52 now part of default MTP AcceptedProfiles: 53 DeniedProfiles: 54 Content: 55 From 6b9c020cd2acad09458532fb15fb8d497b070c89 Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 20 Jul 2026 18:56:48 +0200 Subject: [PATCH 181/220] [Clean] Remove legacy TTP flake configuration --- flake.lock | 19 +-------- flake.nix | 119 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/flake.lock b/flake.lock index 0d30539..4c197a5 100644 --- a/flake.lock +++ b/flake.lock @@ -53,8 +53,7 @@ "inputs": { "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay", - "ttp": "ttp" + "rust-overlay": "rust-overlay" } }, "rust-overlay": { @@ -76,22 +75,6 @@ "repo": "rust-overlay", "type": "github" } - }, - "ttp": { - "flake": false, - "locked": { - "lastModified": 1780494955, - "narHash": "sha256-i2VRRF6yNips3c4JHgfvmvMxb0HTkTCn69lmsKZLHRw=", - "ref": "refs/heads/main", - "rev": "23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2", - "revCount": 125, - "type": "git", - "url": "https://git.methanium.net/tensamin/ttp.git" - }, - "original": { - "type": "git", - "url": "https://git.methanium.net/tensamin/ttp.git" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 070a235..6e40316 100644 --- a/flake.nix +++ b/flake.nix @@ -8,13 +8,16 @@ url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; - ttp = { - url = "git+https://git.methanium.net/tensamin/ttp.git"; - flake = false; - }; }; - outputs = inputs@{ self, nixpkgs, flake-parts, rust-overlay, ttp, ... }: + outputs = + inputs@{ + self, + nixpkgs, + flake-parts, + rust-overlay, + ... + }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" @@ -23,14 +26,25 @@ "aarch64-darwin" ]; - perSystem = { self', pkgs, system, ... }: + perSystem = + { + self', + pkgs, + system, + ... + }: let rustPkgs = import nixpkgs { inherit system; overlays = [ (import rust-overlay) ]; }; rustToolchain = rustPkgs.rust-bin.stable.latest.default.override { - extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; + extensions = [ + "rust-src" + "rust-analyzer" + "clippy" + "rustfmt" + ]; }; in { @@ -44,28 +58,47 @@ lockFile = ./Cargo.lock; allowBuiltinFetchGit = true; }; - nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; - buildInputs = with pkgs; [ openssl libmysqlclient ]; + nativeBuildInputs = with pkgs; [ + cmake + perl + pkg-config + ]; + buildInputs = with pkgs; [ + openssl + libmysqlclient + ]; dontUseCmakeConfigure = true; - preConfigure = '' - if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then - cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json - fi - ''; }; }; devShells.default = pkgs.mkShell { - nativeBuildInputs = with pkgs; [ rustToolchain git cmake perl pkg-config ]; - buildInputs = with pkgs; [ openssl libmysqlclient ]; + nativeBuildInputs = with pkgs; [ + rustToolchain + git + cmake + perl + pkg-config + ]; + buildInputs = with pkgs; [ + openssl + libmysqlclient + ]; }; }; flake = { - nixosModules.default = { config, pkgs, lib, ... }: + nixosModules.default = + { + config, + pkgs, + lib, + ... + }: let cfg = config.services.omega; - defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default or (throw "omega: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); + defaultPackage = + self.packages.${pkgs.stdenv.hostPlatform.system}.default + or (throw "omega: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); in { options.services.omega = { @@ -89,12 +122,12 @@ transportCertFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL certificate file for the TTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; + description = "Path to the SSL certificate file for the MTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; }; transportKeyFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL private key file for the TTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; + description = "Path to the SSL private key file for the MTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; }; environmentFiles = lib.mkOption { @@ -115,13 +148,7 @@ transportPort = lib.mkOption { type = lib.types.port; default = 9187; - description = "Port for the TTP/QUIC transport server."; - }; - - ttpBind = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; - description = "IP address to bind the TTP/QUIC transport server to."; + description = "Port for the MTP/QUIC transport server."; }; bindAddress = lib.mkOption { @@ -171,7 +198,6 @@ Environment = [ "API_PORT=${toString cfg.apiPort}" "OMIKRON_PORT=${toString cfg.transportPort}" - "TTP_BIND=${cfg.ttpBind}" "BIND_ADDRESS=${cfg.bindAddress}" ]; @@ -181,18 +207,21 @@ CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; ExecStartPre = [ - ("+" + pkgs.writeShellScript "omega-setup-certs" '' - mkdir -p ${cfg.dataDir}/certs - cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem - ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.apiKeyFile} \ - -out ${cfg.dataDir}/certs/server_key.pem - cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem - ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.transportKeyFile} \ - -out ${cfg.dataDir}/certs/transport_key.pem - chown -R omega:omega ${cfg.dataDir} - '') + ( + "+" + + pkgs.writeShellScript "omega-setup-certs" '' + mkdir -p ${cfg.dataDir}/certs + cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.apiKeyFile} \ + -out ${cfg.dataDir}/certs/server_key.pem + cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.transportKeyFile} \ + -out ${cfg.dataDir}/certs/transport_key.pem + chown -R omega:omega ${cfg.dataDir} + '' + ) ]; ProtectSystem = "strict"; @@ -211,8 +240,14 @@ }; networking.firewall = lib.mkIf cfg.openFirewall { - allowedTCPPorts = [ cfg.transportPort cfg.apiPort ]; - allowedUDPPorts = [ cfg.transportPort cfg.apiPort ]; + allowedTCPPorts = [ + cfg.transportPort + cfg.apiPort + ]; + allowedUDPPorts = [ + cfg.transportPort + cfg.apiPort + ]; }; }; }; From 4f4b193cedb315513e4b279f099ce23d7c04f8b6 Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 20 Jul 2026 18:56:48 +0200 Subject: [PATCH 182/220] [Clean] Remove legacy TTP flake configuration --- flake.lock | 19 +-------- flake.nix | 119 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 78 insertions(+), 60 deletions(-) diff --git a/flake.lock b/flake.lock index 0d30539..4c197a5 100644 --- a/flake.lock +++ b/flake.lock @@ -53,8 +53,7 @@ "inputs": { "flake-parts": "flake-parts", "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay", - "ttp": "ttp" + "rust-overlay": "rust-overlay" } }, "rust-overlay": { @@ -76,22 +75,6 @@ "repo": "rust-overlay", "type": "github" } - }, - "ttp": { - "flake": false, - "locked": { - "lastModified": 1780494955, - "narHash": "sha256-i2VRRF6yNips3c4JHgfvmvMxb0HTkTCn69lmsKZLHRw=", - "ref": "refs/heads/main", - "rev": "23438fa8f884e6ad0d32ca1004c0dedcce0cc8d2", - "revCount": 125, - "type": "git", - "url": "https://git.methanium.net/tensamin/ttp.git" - }, - "original": { - "type": "git", - "url": "https://git.methanium.net/tensamin/ttp.git" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 070a235..6e40316 100644 --- a/flake.nix +++ b/flake.nix @@ -8,13 +8,16 @@ url = "github:oxalica/rust-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; - ttp = { - url = "git+https://git.methanium.net/tensamin/ttp.git"; - flake = false; - }; }; - outputs = inputs@{ self, nixpkgs, flake-parts, rust-overlay, ttp, ... }: + outputs = + inputs@{ + self, + nixpkgs, + flake-parts, + rust-overlay, + ... + }: flake-parts.lib.mkFlake { inherit inputs; } { systems = [ "x86_64-linux" @@ -23,14 +26,25 @@ "aarch64-darwin" ]; - perSystem = { self', pkgs, system, ... }: + perSystem = + { + self', + pkgs, + system, + ... + }: let rustPkgs = import nixpkgs { inherit system; overlays = [ (import rust-overlay) ]; }; rustToolchain = rustPkgs.rust-bin.stable.latest.default.override { - extensions = [ "rust-src" "rust-analyzer" "clippy" "rustfmt" ]; + extensions = [ + "rust-src" + "rust-analyzer" + "clippy" + "rustfmt" + ]; }; in { @@ -44,28 +58,47 @@ lockFile = ./Cargo.lock; allowBuiltinFetchGit = true; }; - nativeBuildInputs = with pkgs; [ cmake perl pkg-config ]; - buildInputs = with pkgs; [ openssl libmysqlclient ]; + nativeBuildInputs = with pkgs; [ + cmake + perl + pkg-config + ]; + buildInputs = with pkgs; [ + openssl + libmysqlclient + ]; dontUseCmakeConfigure = true; - preConfigure = '' - if [ -d ../cargo-vendor-dir/ttp-core-0.1.0 ]; then - cp ${ttp}/ttp-codec.json ../cargo-vendor-dir/ttp-codec.json - fi - ''; }; }; devShells.default = pkgs.mkShell { - nativeBuildInputs = with pkgs; [ rustToolchain git cmake perl pkg-config ]; - buildInputs = with pkgs; [ openssl libmysqlclient ]; + nativeBuildInputs = with pkgs; [ + rustToolchain + git + cmake + perl + pkg-config + ]; + buildInputs = with pkgs; [ + openssl + libmysqlclient + ]; }; }; flake = { - nixosModules.default = { config, pkgs, lib, ... }: + nixosModules.default = + { + config, + pkgs, + lib, + ... + }: let cfg = config.services.omega; - defaultPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.default or (throw "omega: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); + defaultPackage = + self.packages.${pkgs.stdenv.hostPlatform.system}.default + or (throw "omega: no pre-built package for system ${pkgs.stdenv.hostPlatform.system}"); in { options.services.omega = { @@ -89,12 +122,12 @@ transportCertFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL certificate file for the TTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; + description = "Path to the SSL certificate file for the MTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; }; transportKeyFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL private key file for the TTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; + description = "Path to the SSL private key file for the MTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; }; environmentFiles = lib.mkOption { @@ -115,13 +148,7 @@ transportPort = lib.mkOption { type = lib.types.port; default = 9187; - description = "Port for the TTP/QUIC transport server."; - }; - - ttpBind = lib.mkOption { - type = lib.types.str; - default = "0.0.0.0"; - description = "IP address to bind the TTP/QUIC transport server to."; + description = "Port for the MTP/QUIC transport server."; }; bindAddress = lib.mkOption { @@ -171,7 +198,6 @@ Environment = [ "API_PORT=${toString cfg.apiPort}" "OMIKRON_PORT=${toString cfg.transportPort}" - "TTP_BIND=${cfg.ttpBind}" "BIND_ADDRESS=${cfg.bindAddress}" ]; @@ -181,18 +207,21 @@ CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; ExecStartPre = [ - ("+" + pkgs.writeShellScript "omega-setup-certs" '' - mkdir -p ${cfg.dataDir}/certs - cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem - ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.apiKeyFile} \ - -out ${cfg.dataDir}/certs/server_key.pem - cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem - ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.transportKeyFile} \ - -out ${cfg.dataDir}/certs/transport_key.pem - chown -R omega:omega ${cfg.dataDir} - '') + ( + "+" + + pkgs.writeShellScript "omega-setup-certs" '' + mkdir -p ${cfg.dataDir}/certs + cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.apiKeyFile} \ + -out ${cfg.dataDir}/certs/server_key.pem + cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem + ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ + -in ${cfg.transportKeyFile} \ + -out ${cfg.dataDir}/certs/transport_key.pem + chown -R omega:omega ${cfg.dataDir} + '' + ) ]; ProtectSystem = "strict"; @@ -211,8 +240,14 @@ }; networking.firewall = lib.mkIf cfg.openFirewall { - allowedTCPPorts = [ cfg.transportPort cfg.apiPort ]; - allowedUDPPorts = [ cfg.transportPort cfg.apiPort ]; + allowedTCPPorts = [ + cfg.transportPort + cfg.apiPort + ]; + allowedUDPPorts = [ + cfg.transportPort + cfg.apiPort + ]; }; }; }; From 826fb9ce50b357fedb839583b282c03fb23058dc Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:21:43 +0200 Subject: [PATCH 183/220] [Add] Structure --- Cargo.lock | 720 +++------------- Cargo.toml | 32 +- migrations/001.sql | 44 + src/api/mod.rs | 1 + src/api/response.rs | 70 ++ src/config.rs | 65 ++ src/db/iota_repo.rs | 53 ++ src/db/mod.rs | 38 + src/db/notification_repo.rs | 38 + src/db/omikron_repo.rs | 29 + src/db/short_link_repo.rs | 35 + src/db/user_repo.rs | 194 +++++ src/error.rs | 57 ++ src/main.rs | 53 +- src/models/ids.rs | 30 + src/models/iota.rs | 9 + src/models/mod.rs | 21 + src/models/notification.rs | 9 + src/models/omikron.rs | 12 + src/models/user.rs | 19 + src/server/api.rs | 546 +++++------- src/server/middleware.rs | 84 ++ src/server/mod.rs | 2 + src/server/short_link.rs | 46 +- src/server/validation.rs | 42 + src/sql/mod.rs | 1 - src/sql/sql.rs | 778 ----------------- src/sql/user_online_tracker.rs | 105 ++- src/transport/connection.rs | 1 + src/transport/handlers/account.rs | 43 + src/transport/handlers/calls.rs | 1 + src/transport/handlers/links.rs | 21 + src/transport/handlers/messaging.rs | 1 + src/transport/handlers/mod.rs | 9 + src/transport/handlers/notifications.rs | 116 +++ src/transport/handlers/presence.rs | 128 +++ src/transport/handlers/register.rs | 151 ++++ src/transport/handlers/states.rs | 46 + src/transport/handlers/user_data.rs | 271 ++++++ src/transport/mod.rs | 2 + src/transport/omikron_connection.rs | 1020 ++++------------------- src/transport/omikron_manager.rs | 6 +- src/util/file_util.rs | 5 +- src/util/logger.rs | 3 +- 44 files changed, 2223 insertions(+), 2734 deletions(-) create mode 100644 migrations/001.sql create mode 100644 src/api/mod.rs create mode 100644 src/api/response.rs create mode 100644 src/config.rs create mode 100644 src/db/iota_repo.rs create mode 100644 src/db/mod.rs create mode 100644 src/db/notification_repo.rs create mode 100644 src/db/omikron_repo.rs create mode 100644 src/db/short_link_repo.rs create mode 100644 src/db/user_repo.rs create mode 100644 src/error.rs create mode 100644 src/models/ids.rs create mode 100644 src/models/iota.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/notification.rs create mode 100644 src/models/omikron.rs create mode 100644 src/models/user.rs create mode 100644 src/server/middleware.rs create mode 100644 src/server/validation.rs delete mode 100644 src/sql/sql.rs create mode 100644 src/transport/connection.rs create mode 100644 src/transport/handlers/account.rs create mode 100644 src/transport/handlers/calls.rs create mode 100644 src/transport/handlers/links.rs create mode 100644 src/transport/handlers/messaging.rs create mode 100644 src/transport/handlers/mod.rs create mode 100644 src/transport/handlers/notifications.rs create mode 100644 src/transport/handlers/presence.rs create mode 100644 src/transport/handlers/register.rs create mode 100644 src/transport/handlers/states.rs create mode 100644 src/transport/handlers/user_data.rs diff --git a/Cargo.lock b/Cargo.lock index 4ce5e8d..a20fbae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,27 +18,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "aead" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" -dependencies = [ - "crypto-common 0.2.2", - "inout 0.2.2", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures 0.2.17", -] - [[package]] name = "aes" version = "0.9.1" @@ -50,20 +29,6 @@ dependencies = [ "cpufeatures 0.3.0", ] -[[package]] -name = "aes-gcm" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" -dependencies = [ - "aead 0.6.1", - "aes 0.9.1", - "cipher 0.5.2", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -79,21 +44,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "asn1-rs" version = "0.7.2" @@ -133,148 +83,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.5.0", - "futures-lite 2.6.1", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io 2.6.0", - "async-lock 3.4.2", - "blocking", - "futures-lite 2.6.1", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.6.1", - "parking", - "polling 3.11.0", - "rustix 1.1.4", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io 2.6.0", - "async-lock 3.4.2", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 2.6.1", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" version = "0.1.91" @@ -283,7 +91,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 3.0.2", ] [[package]] @@ -352,12 +160,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.13.1" @@ -383,19 +185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite 2.6.1", - "piper", + "zeroize", ] [[package]] @@ -477,7 +267,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead 0.5.2", + "aead", "chacha20 0.9.1", "cipher 0.4.4", "poly1305", @@ -501,7 +291,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "block-buffer 0.12.1", "crypto-common 0.2.2", "inout 0.2.2", ] @@ -554,9 +343,9 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "core-foundation" @@ -669,15 +458,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "ctr" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" -dependencies = [ - "cipher 0.5.2", -] - [[package]] name = "ctutils" version = "0.4.2" @@ -697,7 +477,7 @@ dependencies = [ "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", - "fiat-crypto 0.2.9", + "fiat-crypto", "rustc_version", "subtle", "zeroize", @@ -781,17 +561,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "digest" version = "0.10.7" @@ -814,6 +583,7 @@ dependencies = [ "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", + "zeroize", ] [[package]] @@ -869,17 +639,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed448-goldilocks" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" -dependencies = [ - "fiat-crypto 0.1.20", - "hex", - "subtle", -] - [[package]] name = "either" version = "1.16.0" @@ -925,12 +684,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "event-listener" version = "5.4.1" @@ -942,16 +695,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.1", - "pin-project-lite", -] - [[package]] name = "fastbloom" version = "0.17.0" @@ -964,27 +707,12 @@ dependencies = [ "siphasher", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1110,34 +838,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand 2.5.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.33" @@ -1201,18 +901,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -1222,32 +910,11 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi 6.0.0", + "r-efi", "rand_core 0.10.1", "wasm-bindgen", ] -[[package]] -name = "ghash" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" -dependencies = [ - "polyval", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.4.15" @@ -1274,7 +941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" dependencies = [ "bytes", - "fastrand 2.5.0", + "fastrand", "futures-util", "http", "pin-project-lite", @@ -1361,18 +1028,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -1481,9 +1136,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -1532,7 +1187,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.5", + "socket2", "system-configuration", "tokio", "tower-service", @@ -1671,26 +1326,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -1773,12 +1408,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - [[package]] name = "keccak" version = "0.1.6" @@ -1798,15 +1427,6 @@ dependencies = [ "cpufeatures 0.3.0", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1840,7 +1460,7 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.13.1", + "bitflags", "libc", "plain", "redox_syscall 0.9.0", @@ -1856,18 +1476,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - [[package]] name = "litemap" version = "0.8.2" @@ -1888,9 +1496,6 @@ name = "log" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" -dependencies = [ - "value-bag", -] [[package]] name = "lru-slab" @@ -1900,12 +1505,11 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "lzma-rust2" -version = "0.13.0" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" dependencies = [ - "crc", - "sha2 0.10.9", + "sha2 0.11.0", ] [[package]] @@ -2252,31 +1856,25 @@ dependencies = [ name = "omega" version = "0.1.0" dependencies = [ - "aes-gcm", "ansi_term", - "anyhow", "base64", "bytes", "dashmap", "dotenv", - "hex", - "hkdf 0.12.4", "http", - "json", "mtp", "once_cell", - "rand 0.8.7", - "rand_core 0.6.4", + "rand 0.10.2", "reqwest", "rustls", - "sha2 0.10.9", + "serde", + "serde_json", "sqlx", "strum", "strum_macros", "thiserror 2.0.19", "tokio", "uuid", - "x448", "zip", ] @@ -2329,12 +1927,12 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.12.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", + "digest 0.11.3", + "hmac 0.13.0", ] [[package]] @@ -2368,23 +1966,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand 2.5.0", - "futures-io", -] - [[package]] name = "pkcs1" version = "0.7.5" @@ -2428,36 +2009,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.5.2", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - [[package]] name = "poly1305" version = "0.8.0" @@ -2466,18 +2017,7 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash 0.5.1", -] - -[[package]] -name = "polyval" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" -dependencies = [ - "cpubits", - "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", ] [[package]] @@ -2539,7 +2079,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.5", + "socket2", "thiserror 2.0.19", "tokio", "tracing", @@ -2580,7 +2120,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.5", + "socket2", "tracing", "windows-sys 0.61.2", ] @@ -2594,12 +2134,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -2638,12 +2172,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" @@ -2689,7 +2217,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags", ] [[package]] @@ -2698,7 +2226,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ - "bitflags 2.13.1", + "bitflags", ] [[package]] @@ -2797,33 +2325,6 @@ dependencies = [ "nom", ] -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - [[package]] name = "rustls" version = "0.23.42" @@ -2943,7 +2444,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2993,14 +2494,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 3.0.2", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -3045,6 +2546,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3167,16 +2679,6 @@ dependencies = [ "serde", ] -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.6.5" @@ -3241,14 +2743,12 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "async-io 1.13.0", - "async-std", "base64", "bytes", "crc", "crossbeam-queue", "either", - "event-listener 5.4.1", + "event-listener", "futures-core", "futures-intrusive", "futures-io", @@ -3265,6 +2765,8 @@ dependencies = [ "sha2 0.10.9", "smallvec", "thiserror 2.0.19", + "tokio", + "tokio-stream", "tracing", "url", ] @@ -3288,7 +2790,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ - "async-std", "dotenvy", "either", "heck", @@ -3304,6 +2805,7 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "tokio", "url", ] @@ -3315,7 +2817,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.13.1", + "bitflags", "byteorder", "bytes", "crc", @@ -3339,7 +2841,7 @@ dependencies = [ "rand 0.8.7", "rsa", "serde", - "sha1", + "sha1 0.10.7", "sha2 0.10.9", "smallvec", "sqlx-core", @@ -3357,7 +2859,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.13.1", + "bitflags", "byteorder", "crc", "dotenvy", @@ -3429,15 +2931,15 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", @@ -3464,9 +2966,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" dependencies = [ "proc-macro2", "quote", @@ -3499,7 +3001,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.1", + "bitflags", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3551,16 +3053,17 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 3.0.2", ] [[package]] name = "time" -version = "0.3.53" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -3576,9 +3079,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -3611,9 +3114,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -3621,7 +3124,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.5", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3647,6 +3150,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -3681,7 +3195,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags", "bytes", "futures-util", "http", @@ -3743,6 +3257,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.20.1" @@ -3786,16 +3306,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "universal-hash" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" -dependencies = [ - "crypto-common 0.2.2", - "ctutils", -] - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3843,12 +3353,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "value-bag" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd4ec1eb1d240636e354a30110a1dfcb37047169a4d9bd6d9d3469df574b5c4" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3861,12 +3365,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -3892,15 +3390,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasite" version = "0.1.0" @@ -4215,12 +3704,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "writeable" version = "0.6.3" @@ -4241,7 +3724,7 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2 0.6.5", + "socket2", "thiserror 2.0.19", "time", "tokio", @@ -4275,17 +3758,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "x448" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" -dependencies = [ - "ed448-goldilocks", - "hex", - "rand_core 0.5.1", -] - [[package]] name = "x509-parser" version = "0.18.1" @@ -4340,18 +3812,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", @@ -4434,26 +3906,26 @@ dependencies = [ [[package]] name = "zip" -version = "6.0.0" +version = "8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ - "aes 0.8.4", - "arbitrary", + "aes", "bzip2", "constant_time_eq", "crc32fast", "deflate64", "flate2", - "getrandom 0.3.4", - "hmac 0.12.1", + "getrandom 0.4.3", + "hmac 0.13.0", "indexmap", "lzma-rust2", "memchr", "pbkdf2", "ppmd-rust", - "sha1", + "sha1 0.11.0", "time", + "typed-path", "zeroize", "zopfli", "zstd", diff --git a/Cargo.toml b/Cargo.toml index e601d1a..3b4f6a3 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,33 +10,27 @@ mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ "web-server", ] } -aes-gcm = "*" ansi_term = "0.12.1" -anyhow = "1.0.101" base64 = "0.22.1" bytes = "1" -dashmap = "6.1.0" +dashmap = "6.2.1" dotenv = "0.15.0" -hex = "0.4.3" -hkdf = "0.12.4" http = "1" -json = "0.12.4" -once_cell = "1.21.3" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = { version = "0.13.2" } -rustls = { version = "0.23.37", default-features = false, features = [ +once_cell = "1.21.4" +rand = "0.10.2" +reqwest = "0.13.4" +rustls = { version = "0.23.42", default-features = false, features = [ "std", "tls12", "aws-lc-rs", "prefer-post-quantum", ] } -sha2 = "0.10.9" -sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } -strum = "0.27.2" -strum_macros = "0.27.2" +sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } +strum = "0.28.0" +strum_macros = "0.28.0" tokio = { version = "*", features = ["full"] } -uuid = { version = "1.19.0", features = ["v4"] } -x448 = "0.6.0" -zip = "6.0.0" -thiserror = "2.0.18" +uuid = { version = "1.24.0", features = ["v4", "v7"] } +zip = "8.6.0" +thiserror = "2.0.19" +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" diff --git a/migrations/001.sql b/migrations/001.sql new file mode 100644 index 0000000..dc3a096 --- /dev/null +++ b/migrations/001.sql @@ -0,0 +1,44 @@ +CREATE TABLE IF NOT EXISTS users ( + id BIGINT NOT NULL PRIMARY KEY, + iota_id BIGINT NOT NULL, + username VARBINARY(255) NOT NULL, + display VARBINARY(255), + status VARBINARY(255), + about VARBINARY(1000), + avatar BLOB, + sub_level INT NOT NULL DEFAULT 0, + sub_end BIGINT NOT NULL DEFAULT 0, + public_key BLOB NOT NULL, + token BLOB NOT NULL, + UNIQUE KEY uk_users_username (username), + UNIQUE KEY uk_users_iota_id (iota_id) +); + +CREATE TABLE IF NOT EXISTS iotas ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL +); + +CREATE TABLE IF NOT EXISTS omikrons ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL, + location VARBINARY(255) NOT NULL, + ip_address VARBINARY(45) NOT NULL, + port INT NOT NULL +); + +CREATE TABLE IF NOT EXISTS notifications ( + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + sender_id BIGINT NOT NULL, + receiver_id BIGINT NOT NULL, + amount BIGINT NOT NULL DEFAULT 1, + UNIQUE KEY uk_notifications_sender_receiver (sender_id, receiver_id), + INDEX idx_notifications_receiver (receiver_id) +); + +CREATE TABLE IF NOT EXISTS short_links ( + short_key VARCHAR(12) CHARACTER SET ascii COLLATE ascii_bin NOT NULL PRIMARY KEY, + long_url TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_short_links_created_at (created_at) +); diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..4c6f2cd --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1 @@ +pub mod response; diff --git a/src/api/response.rs b/src/api/response.rs new file mode 100644 index 0000000..e8489b5 --- /dev/null +++ b/src/api/response.rs @@ -0,0 +1,70 @@ +use serde::Serialize; +use std::collections::BTreeMap; + +#[derive(Serialize)] +pub struct StatusResponse { + pub status: &'static str, +} + +#[derive(Serialize)] +pub struct OmikronResponse { + pub status: &'static str, + pub id: i64, + pub public_key: String, + pub ip_address: String, + pub port: u16, +} + +#[derive(Serialize)] +pub struct IotaResponse { + pub status: &'static str, + pub iota_id: i64, + pub public_key: String, +} + +#[derive(Serialize)] +pub struct UserResponse { + pub status: &'static str, + pub username: String, + pub public_key: String, + pub user_id: i64, + pub iota_id: i64, + pub sub_level: i32, + pub sub_end: i64, + #[serde(skip_serializing_if = "Option::is_none")] + pub display: Option, + #[serde(rename = "status_message", skip_serializing_if = "Option::is_none")] + pub status_message: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub about: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub avatar: Option, +} + +#[derive(Serialize)] +pub struct UsernameResponse { + pub status: &'static str, + pub username: String, + pub public_key: String, + pub user_id: i64, + pub iota_id: i64, + pub sub_level: i32, + pub sub_end: i64, +} + +#[derive(Serialize)] +pub struct ConnectionsResponse { + pub status: &'static str, + #[serde(flatten)] + pub connections: BTreeMap>>, +} + +#[derive(Serialize)] +pub struct PublicKeyResponse { + pub status: &'static str, + pub public_key: String, +} + +pub fn json(value: &T) -> String { + serde_json::to_string(value).unwrap_or_else(|_| "{\"status\":\"error\"}".to_string()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5ba9dc2 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,65 @@ +use std::{env, time::Duration}; + +#[derive(Clone, Debug)] +pub struct RateLimitConfig { + pub window: Duration, + pub general_requests: usize, + pub registration_requests: usize, + pub transport_connections: usize, + pub transport_connections_per_ip: usize, +} + +pub fn cors_origin() -> String { + env::var("CORS_ORIGIN").unwrap_or_else(|_| "https://tensamin.net".to_string()) +} + +impl Default for RateLimitConfig { + fn default() -> Self { + Self { + window: Duration::from_secs(60), + general_requests: 120, + registration_requests: 20, + transport_connections: 512, + transport_connections_per_ip: 32, + } + } +} + +impl RateLimitConfig { + pub fn from_env() -> Self { + let defaults = Self::default(); + Self { + window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window), + general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests), + registration_requests: env_usize( + "RATE_LIMIT_REGISTRATION_REQUESTS", + defaults.registration_requests, + ), + transport_connections: env_usize( + "RATE_LIMIT_TRANSPORT_CONNECTIONS", + defaults.transport_connections, + ), + transport_connections_per_ip: env_usize( + "RATE_LIMIT_TRANSPORT_CONNECTIONS_PER_IP", + defaults.transport_connections_per_ip, + ), + } + } +} + +fn env_usize(name: &str, fallback: usize) -> usize { + env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(fallback) +} + +fn env_duration(name: &str, fallback: Duration) -> Duration { + env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .map(Duration::from_secs) + .unwrap_or(fallback) +} diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs new file mode 100644 index 0000000..a5952f1 --- /dev/null +++ b/src/db/iota_repo.rs @@ -0,0 +1,53 @@ +use crate::{ + db::pool, + error::{OmegaError, Result}, + models::{Iota, IotaId}, +}; +use mtp::crypto::PublicKeyBundle; +use sqlx::Row; + +pub async fn get_iota_by_id(id: IotaId) -> Result { + let row = sqlx::query("SELECT id, public_key FROM iotas WHERE id = ?") + .bind(id.0) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + let key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|error| OmegaError::Validation(error.to_string()))?; + Ok(Iota { + id: row.get::("id").into(), + public_key: key, + }) +} + +pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { + let id = crate::db::user_repo::get_register_id().await?; + let iota_id = IotaId::from(id.0); + register_complete_iota(iota_id, public_key).await?; + Ok(iota_id) +} + +pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> Result<()> { + sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") + .bind(id.0) + .bind(public_key.as_bytes()) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> { + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + .bind(key.as_bytes()) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} +pub async fn delete_iota(id: IotaId) -> Result<()> { + sqlx::query("DELETE FROM iotas WHERE id = ?") + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..f8afccb --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,38 @@ +use crate::error::{OmegaError, Result}; +use once_cell::sync::Lazy; +use sqlx::{MySql, Pool, mysql::MySqlPoolOptions}; +use std::{env, sync::Arc}; +use tokio::sync::RwLock; + +pub mod iota_repo; +pub mod notification_repo; +pub mod omikron_repo; +pub mod short_link_repo; +pub mod user_repo; + +pub type DbPool = Pool; + +static POOL: Lazy>>> = Lazy::new(|| Arc::new(RwLock::new(None))); + +pub async fn pool() -> Result { + POOL.read() + .await + .as_ref() + .cloned() + .ok_or(OmegaError::DatabaseNotInitialized) +} + +pub async fn initialize() -> Result<()> { + let url = + env::var("DB_URL").map_err(|_| OmegaError::Validation("DB_URL is not set".to_string()))?; + let pool = MySqlPoolOptions::new() + .max_connections(200) + .connect(&url) + .await?; + sqlx::migrate!() + .run(&pool) + .await + .map_err(|error| OmegaError::Validation(format!("database migration failed: {error}")))?; + *POOL.write().await = Some(pool); + Ok(()) +} diff --git a/src/db/notification_repo.rs b/src/db/notification_repo.rs new file mode 100644 index 0000000..b317472 --- /dev/null +++ b/src/db/notification_repo.rs @@ -0,0 +1,38 @@ +use crate::{ + db::pool, + error::Result, + models::{Notification, UserId}, +}; +use sqlx::Row; + +pub async fn add_notification(sender_id: UserId, receiver_id: UserId) -> Result<()> { + sqlx::query("INSERT INTO notifications (sender_id, receiver_id, amount) VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE amount = amount + 1").bind(sender_id.0).bind(receiver_id.0).execute(&pool().await?).await?; + Ok(()) +} + +pub async fn read_notification(sender_id: UserId, receiver_id: UserId) -> Result<()> { + sqlx::query("DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ?") + .bind(sender_id.0) + .bind(receiver_id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn get_notifications(receiver_id: UserId) -> Result> { + let rows = sqlx::query( + "SELECT id, sender_id, receiver_id, amount FROM notifications WHERE receiver_id = ?", + ) + .bind(receiver_id.0) + .fetch_all(&pool().await?) + .await?; + Ok(rows + .into_iter() + .map(|row| Notification { + id: row.get("id"), + sender_id: row.get::("sender_id").into(), + receiver_id: row.get::("receiver_id").into(), + amount: row.get("amount"), + }) + .collect()) +} diff --git a/src/db/omikron_repo.rs b/src/db/omikron_repo.rs new file mode 100644 index 0000000..03e1661 --- /dev/null +++ b/src/db/omikron_repo.rs @@ -0,0 +1,29 @@ +use crate::{ + db::pool, + error::{OmegaError, Result}, + models::{Omikron, OmikronId}, +}; +use mtp::crypto::PublicKeyBundle; +use sqlx::Row; + +pub async fn get_omikron_by_id(id: OmikronId) -> Result { + let row = + sqlx::query("SELECT id, public_key, location, ip_address, port FROM omikrons WHERE id = ?") + .bind(id.0) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + let key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|error| OmegaError::Validation(error.to_string()))?; + let location = String::from_utf8(row.get("location")) + .map_err(|error| OmegaError::Validation(format!("invalid location UTF-8: {error}")))?; + let ip_address = String::from_utf8(row.get("ip_address")) + .map_err(|error| OmegaError::Validation(format!("invalid IP address UTF-8: {error}")))?; + Ok(Omikron { + id: row.get::("id").into(), + public_key: key, + location, + ip_address, + port: row.get::("port") as u16, + }) +} diff --git a/src/db/short_link_repo.rs b/src/db/short_link_repo.rs new file mode 100644 index 0000000..24d85ac --- /dev/null +++ b/src/db/short_link_repo.rs @@ -0,0 +1,35 @@ +use crate::{db::pool, error::Result}; + +pub async fn count() -> Result { + let count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM short_links") + .fetch_one(&pool().await?) + .await?; + Ok(count.max(0) as u64) +} + +pub async fn insert(short_key: &str, long_url: &str) -> Result { + let result = sqlx::query("INSERT IGNORE INTO short_links (short_key, long_url) VALUES (?, ?)") + .bind(short_key) + .bind(long_url) + .execute(&pool().await?) + .await?; + Ok(result.rows_affected() == 1) +} + +pub async fn get(short_key: &str) -> Result> { + Ok( + sqlx::query_scalar::<_, String>("SELECT long_url FROM short_links WHERE short_key = ?") + .bind(short_key) + .fetch_optional(&pool().await?) + .await?, + ) +} + +pub async fn delete_expired() -> Result { + let result = sqlx::query( + "DELETE FROM short_links WHERE created_at < CURRENT_TIMESTAMP - INTERVAL 7 DAY", + ) + .execute(&pool().await?) + .await?; + Ok(result.rows_affected()) +} diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs new file mode 100644 index 0000000..ca71dd3 --- /dev/null +++ b/src/db/user_repo.rs @@ -0,0 +1,194 @@ +use crate::{ + db::pool, + error::{OmegaError, Result}, + models::{IotaId, User, UserId}, +}; +use mtp::crypto::PublicKeyBundle; +use sqlx::FromRow; + +pub async fn get_register_id() -> Result { + let bytes = *uuid::Uuid::now_v7().as_bytes(); + let id = + i64::from_be_bytes(bytes[8..].try_into().map_err(|_| { + OmegaError::Validation("generated ID has an invalid length".to_string()) + })?) & i64::MAX; + Ok(UserId::from(id.max(1))) +} + +const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; +const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; +const USERS_BY_IOTA_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE iota_id = ?"; + +#[derive(FromRow)] +struct UserRow { + id: i64, + iota_id: i64, + username: Vec, + display: Option>, + status: Option>, + about: Option>, + avatar: Option>, + sub_level: i32, + sub_end: i64, + public_key: Vec, + token: Vec, +} + +impl TryFrom for User { + type Error = sqlx::Error; + + fn try_from(row: UserRow) -> std::result::Result { + let public_key = PublicKeyBundle::from_bytes(&row.public_key) + .map_err(|error| sqlx::Error::Decode(Box::new(error)))?; + let decode = + |value| String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error))); + Ok(User { + id: row.id.into(), + iota_id: row.iota_id.into(), + username: decode(row.username)?, + display: row.display.map(decode).transpose()?, + status: row.status.map(decode).transpose()?, + about: row.about.map(decode).transpose()?, + avatar: row.avatar, + sub_level: row.sub_level, + sub_end: row.sub_end, + public_key, + token: decode(row.token)?, + }) + } +} + +pub async fn get_by_username(username: &str) -> Result { + let row = sqlx::query_as::<_, UserRow>(USER_BY_USERNAME_QUERY) + .bind(username) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + row.try_into().map_err(OmegaError::from) +} + +pub async fn get_by_user_id(id: UserId) -> Result { + let row = sqlx::query_as::<_, UserRow>(USER_BY_ID_QUERY) + .bind(id.0) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + row.try_into().map_err(OmegaError::from) +} + +pub async fn get_users_by_iota_id(id: IotaId) -> Result> { + let rows = sqlx::query_as::<_, UserRow>(USERS_BY_IOTA_ID_QUERY) + .bind(id.0) + .fetch_all(&pool().await?) + .await?; + rows.into_iter() + .map(|row| row.try_into().map_err(OmegaError::from)) + .collect() +} + +async fn update( + id: UserId, + query: &'static str, + value: impl Send + sqlx::Encode<'static, sqlx::MySql> + sqlx::Type + 'static, +) -> Result<()> { + sqlx::query(query) + .bind(value) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn change_username(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET username = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_display_name(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET display = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_avatar(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET avatar = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_about(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET about = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_status(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET status = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} + +pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> { + sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") + .bind(value.0) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} +pub async fn change_token(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET token = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn delete_user(id: UserId) -> Result<()> { + sqlx::query("DELETE FROM users WHERE id = ?") + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> { + sqlx::query("UPDATE users SET public_key = ? WHERE id = ?") + .bind(public_key.as_bytes()) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn register_complete_user( + id: UserId, + username: String, + public_key: PublicKeyBundle, + iota_id: IotaId, + token: String, +) -> Result<()> { + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id.0) + .bind(username.into_bytes()) + .bind(public_key.as_bytes()) + .bind(iota_id.0) + .bind(token.into_bytes()) + .execute(&pool().await?) + .await?; + Ok(()) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..9911d25 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,57 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum OmegaError { + #[error("database pool is not initialized")] + DatabaseNotInitialized, + #[error("database error: {0}")] + Database(sqlx::Error), + #[error("invalid input: {0}")] + Validation(String), + #[error("resource not found")] + NotFound, + #[error("transport error: {0}")] + Transport(String), + #[error("not connected")] + NotConnected, + #[error("not authenticated")] + NotAuthenticated, + #[error("invalid response")] + InvalidResponse, + #[error("authentication failed")] + AuthenticationFailed, + #[error("send error: {0}")] + SendError(String), + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), +} + +pub type Result = std::result::Result; + +impl From for OmegaError { + fn from(error: sqlx::Error) -> Self { + if matches!(error, sqlx::Error::RowNotFound) { + Self::NotFound + } else { + Self::Database(error) + } + } +} + +impl OmegaError { + pub fn status_code(&self) -> http::StatusCode { + match self { + Self::Validation(_) => http::StatusCode::BAD_REQUEST, + Self::NotFound => http::StatusCode::NOT_FOUND, + Self::DatabaseNotInitialized + | Self::Database(_) + | Self::Transport(_) + | Self::NotConnected + | Self::NotAuthenticated + | Self::InvalidResponse + | Self::AuthenticationFailed + | Self::SendError(_) + | Self::Io(_) => http::StatusCode::INTERNAL_SERVER_ERROR, + } + } +} diff --git a/src/main.rs b/src/main.rs index 9e35297..251713e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,41 @@ +mod api; +mod config; +mod db; +pub mod error; +mod models; mod server; mod sql; mod transport; mod util; -use crate::sql::sql::initialize_db; -use crate::sql::sql::print_users; +pub use error::{OmegaError, Result}; + +use crate::db::initialize; use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; use dotenv::from_path; -use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; use mtp::crypto::Keyring; +use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; +use std::time::Duration; +use tokio::time::interval; const KEYRING_PATH: &str = "./omega.mk"; static KEYRING: Lazy = Lazy::new(|| { load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| { let kr = Keyring::generate(); - save_keyring_raw(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); - save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) - .expect("Failed to save generated public key bundle"); + if let Err(error) = save_keyring_raw(&kr, KEYRING_PATH) { + eprintln!("Failed to save generated keyring: {error}"); + } + if let Err(error) = save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) { + eprintln!("Failed to save generated public key bundle: {error}"); + } eprintln!("Generated new keyring at {}", KEYRING_PATH); kr }) @@ -34,7 +45,10 @@ pub fn get_keyring() -> &'static Keyring { &KEYRING } pub fn load_keyring() -> Keyring { - Keyring::from_bytes(&KEYRING.to_bytes()).unwrap() + Keyring::from_bytes(&KEYRING.to_bytes()).unwrap_or_else(|error| { + eprintln!("Failed to clone keyring: {error}"); + Keyring::generate() + }) } #[tokio::main] @@ -50,7 +64,7 @@ async fn main() { log!("Started"); log!(" .env"); - if let Err(e) = initialize_db().await { + if let Err(e) = initialize().await { log!("[FATAL] Database initialization failed: {}", e); log!( "[FATAL] Please ensure the database is running and the .env file is configured correctly." @@ -59,12 +73,21 @@ async fn main() { } else { log!(" DB"); } - if let Err(e) = print_users().await { - log!("[ERROR] Failed to print users: {}", e); - } else { - log!(" Users"); - } - + let rate_limit_cleanup = crate::server::middleware::spawn_cleanup_task(); + let short_link_cleanup = tokio::spawn(async { + let mut ticker = interval(Duration::from_secs(24 * 60 * 60)); + loop { + ticker.tick().await; + if let Err(error) = crate::db::short_link_repo::delete_expired().await { + log_err!( + 0, + PrintType::General, + "Short-link cleanup failed: {}", + error + ); + } + } + }); let port: u16 = env::var("PORT") .ok() .and_then(|s| s.parse().ok()) @@ -80,4 +103,6 @@ async fn main() { log!("Shutting down on signal..."); } } + rate_limit_cleanup.abort(); + short_link_cleanup.abort(); } diff --git a/src/models/ids.rs b/src/models/ids.rs new file mode 100644 index 0000000..7eda84f --- /dev/null +++ b/src/models/ids.rs @@ -0,0 +1,30 @@ +use std::fmt::{Display, Formatter}; + +macro_rules! id_type { + ($name:ident) => { + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)] + pub struct $name(pub i64); + + impl From for $name { + fn from(value: i64) -> Self { + Self(value) + } + } + + impl From<$name> for i64 { + fn from(value: $name) -> Self { + value.0 + } + } + + impl Display for $name { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(formatter) + } + } + }; +} + +id_type!(UserId); +id_type!(IotaId); +id_type!(OmikronId); diff --git a/src/models/iota.rs b/src/models/iota.rs new file mode 100644 index 0000000..3a93bcb --- /dev/null +++ b/src/models/iota.rs @@ -0,0 +1,9 @@ +use super::IotaId; +use mtp::crypto::PublicKeyBundle; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct Iota { + pub id: IotaId, + #[serde(serialize_with = "crate::models::serialize_public_key")] + pub public_key: PublicKeyBundle, +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..291729d --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,21 @@ +mod ids; +mod iota; +mod notification; +mod omikron; +mod user; + +fn serialize_public_key( + key: &mtp::crypto::PublicKeyBundle, + serializer: S, +) -> std::result::Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&key.to_base64()) +} + +pub use ids::{IotaId, OmikronId, UserId}; +pub use iota::Iota; +pub use notification::Notification; +pub use omikron::Omikron; +pub use user::User; diff --git a/src/models/notification.rs b/src/models/notification.rs new file mode 100644 index 0000000..8dee06e --- /dev/null +++ b/src/models/notification.rs @@ -0,0 +1,9 @@ +use super::UserId; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct Notification { + pub id: i64, + pub sender_id: UserId, + pub receiver_id: UserId, + pub amount: i64, +} diff --git a/src/models/omikron.rs b/src/models/omikron.rs new file mode 100644 index 0000000..3b47d44 --- /dev/null +++ b/src/models/omikron.rs @@ -0,0 +1,12 @@ +use super::OmikronId; +use mtp::crypto::PublicKeyBundle; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct Omikron { + pub id: OmikronId, + #[serde(serialize_with = "crate::models::serialize_public_key")] + pub public_key: PublicKeyBundle, + pub location: String, + pub ip_address: String, + pub port: u16, +} diff --git a/src/models/user.rs b/src/models/user.rs new file mode 100644 index 0000000..0dda84c --- /dev/null +++ b/src/models/user.rs @@ -0,0 +1,19 @@ +use super::{IotaId, UserId}; +use mtp::crypto::PublicKeyBundle; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct User { + pub id: UserId, + pub iota_id: IotaId, + pub username: String, + pub display: Option, + pub status: Option, + pub about: Option, + pub avatar: Option>, + pub sub_level: i32, + pub sub_end: i64, + #[serde(serialize_with = "crate::models::serialize_public_key")] + pub public_key: PublicKeyBundle, + #[serde(skip_serializing)] + pub token: String, +} diff --git a/src/server/api.rs b/src/server/api.rs index 6471398..6dc01aa 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,356 +1,240 @@ +use crate::api::response::{ + ConnectionsResponse, IotaResponse, OmikronResponse, PublicKeyResponse, StatusResponse, + UserResponse, UsernameResponse, json, +}; +use crate::db::{ + iota_repo::get_iota_by_id, + omikron_repo::get_omikron_by_id, + user_repo::{get_by_user_id, get_by_username}, +}; +use crate::error::{OmegaError, Result}; use crate::load_keyring; -use crate::sql::sql; -use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; +use crate::models::UserId; +use crate::server::{ + middleware, + validation::{parse_positive_id, validate_non_empty}, +}; use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use base64::Engine as _; use bytes::Bytes; use http::{Method, StatusCode}; -use json::JsonValue; use mtp::webserver::{Http3Request, Http3Response, RouteParams}; +use std::collections::BTreeMap; + +fn error_body(error: &OmegaError) -> String { + json(&StatusResponse { + status: match error { + OmegaError::Validation(_) => "error_bad_request", + OmegaError::NotFound => "error_not_found", + _ => "error", + }, + }) +} + +fn user_response(user: crate::models::User) -> UserResponse { + UserResponse { + status: "success", + username: user.username, + public_key: user.public_key.to_base64(), + user_id: user.id.0, + iota_id: user.iota_id.0, + sub_level: user.sub_level, + sub_end: user.sub_end, + display: user.display, + status_message: user.status, + about: user.about, + avatar: user + .avatar + .map(|value| base64::engine::general_purpose::STANDARD.encode(value)), + } +} + +async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { + match path_parts { + ["api", "get", "omikron"] => { + let connection = get_random_omikron() + .await + .map_err(|_| OmegaError::NotFound)?; + let id = connection + .get_omikron_id() + .await + .ok_or(OmegaError::NotFound)?; + let omikron = get_omikron_by_id(id.into()).await?; + Ok(( + StatusCode::OK, + json(&OmikronResponse { + status: "success", + id, + public_key: omikron.public_key.to_base64(), + ip_address: omikron.ip_address, + port: omikron.port, + }), + )) + } + ["api", "get", "omikron", id] => { + let id = parse_positive_id(id)?; + let omikron = match get_omikron_by_id(id.into()).await { + Ok(value) => value, + Err(_) => { + let fallback_id = + if let Some(fallback_id) = get_iota_primary_omikron_connection(id) { + fallback_id + } else { + let user = get_by_user_id(UserId::from(id)).await?; + get_iota_primary_omikron_connection(user.iota_id.0) + .ok_or(OmegaError::NotFound)? + }; + get_omikron_by_id(fallback_id.into()).await? + } + }; + Ok(( + StatusCode::OK, + json(&OmikronResponse { + status: "success", + id: omikron.id.0, + public_key: omikron.public_key.to_base64(), + ip_address: omikron.ip_address, + port: omikron.port, + }), + )) + } + ["api", "get", "connections"] => { + let connections = get_all_connections() + .await + .map_err(|_| OmegaError::Transport("failed to load connections".to_string()))?; + let connections = connections + .into_iter() + .map(|(omikron_id, iotas)| { + let iotas = iotas + .into_iter() + .map(|(iota_id, users)| { + ( + iota_id.to_string(), + users.into_iter().map(i64::from).collect(), + ) + }) + .collect(); + (omikron_id.to_string(), iotas) + }) + .collect::>(); + Ok(( + StatusCode::OK, + json(&ConnectionsResponse { + status: "success", + connections, + }), + )) + } + ["api", "get", "iota", id] => { + let id = parse_positive_id(id)?; + let iota = get_iota_by_id(id.into()).await?; + Ok(( + StatusCode::OK, + json(&IotaResponse { + status: "success", + iota_id: iota.id.0, + public_key: iota.public_key.to_base64(), + }), + )) + } + ["api", "get", "id", username] => { + validate_non_empty(username, "username", 15)?; + let user = get_by_username(username).await?; + Ok(( + StatusCode::OK, + json(&UsernameResponse { + status: "success", + username: user.username, + public_key: user.public_key.to_base64(), + user_id: user.id.0, + iota_id: user.iota_id.0, + sub_level: user.sub_level, + sub_end: user.sub_end, + }), + )) + } + ["api", "get", "public_key"] => { + let public_key = base64::engine::general_purpose::STANDARD + .encode(load_keyring().public_key_bundle().as_bytes()); + Ok(( + StatusCode::OK, + json(&PublicKeyResponse { + status: "success", + public_key, + }), + )) + } + ["api", "get", "user", id] => { + let id = parse_positive_id(id)?; + let user = get_by_user_id(UserId::from(id)).await?; + Ok((StatusCode::OK, json(&user_response(user)))) + } + _ => Ok(( + StatusCode::INTERNAL_SERVER_ERROR, + json(&StatusResponse { status: "error" }), + )), + } +} pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Response { let method = request.method; let path = request.uri.path().to_string(); - let body_string = request - .body - .map(|body| String::from_utf8_lossy(&body).to_string()); - + if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { + return response + .status(StatusCode::TOO_MANY_REQUESTS) + .header("access-control-allow-origin", &crate::config::cors_origin()) + .body(json(&StatusResponse { + status: "error_rate_limited", + })); + } if method == Method::OPTIONS { return response .status(StatusCode::OK) - .header("access-control-allow-origin", "*") + .header("access-control-allow-origin", &crate::config::cors_origin()) .header("access-control-allow-methods", "GET, POST, OPTIONS") .header("access-control-allow-headers", "*"); } - - let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); - - let _body: Option = if let Some(ref bs) = body_string { - if let Ok(body_json) = json::parse(bs) { - Some(body_json) - } else { - None - } - } else { - None - }; - - let (status, body_text) = match path_parts.as_slice() { - // ================================================== - // DOWNLOAD IOTA FRONTEND - // ================================================== - ["api", "download", "iota_frontend"] => { - let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); - - match std::fs::read(file_path) { - Ok(file_bytes) => { - return response - .status(StatusCode::OK) - .header("access-control-allow-origin", "*") - .header("content-type", "application/zip") - .header( - "content-disposition", - "attachment; filename=\"iota_frontend.zip\"", - ) - .body(Bytes::from(file_bytes)); - } - Err(_) => { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - return response - .status(StatusCode::NOT_FOUND) - .header("access-control-allow-origin", "*") - .body(res.dump()); - } - } - } - - // ================================================== - // GET RANDOM OMIKRON - // ================================================== - ["api", "get", "omikron"] => { - if let Ok(omikron_conn) = get_random_omikron().await { - if let Some(id) = omikron_conn.get_omikron_id().await { - if let Ok((public_key, ip_address, port)) = sql::get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET OMIKRON BY ID - // ================================================== - ["api", "get", "omikron", id] => { - let id = id.parse::().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((public_key, ip_address, port)) = get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - (StatusCode::OK, res.dump()) - } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await - { - if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - ["api", "get", "connections"] => { - if let Ok(connections) = get_all_connections().await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - - for (omikron_id, iota_map) in connections { - let mut omikron_obj = JsonValue::new_object(); - for (iota_id, user_ids) in iota_map { - let mut user_arr = JsonValue::new_array(); - for user_id in user_ids { - let _ = user_arr.push(user_id); - } - omikron_obj[&iota_id.to_string()] = user_arr; - } - res[&omikron_id.to_string()] = omikron_obj; - } - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } - - // ================================================== - // GET IOTA BY ID - // ================================================== - ["api", "get", "iota", id] => { - let id: i64 = id.parse().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((id, public_key)) = get_iota_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["iota_id"] = id.into(); - res["public_key"] = public_key.to_base64().into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET ID BY USERNAME - // ================================================== - ["api", "get", "id", username] => { - if username.is_empty() { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - _, - _, - _, - _, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.to_base64().into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // GET SERVER PUBLIC KEY - // ================================================== - ["api", "get", "public_key"] => { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - let bundle = load_keyring().public_key_bundle(); - res["public_key"] = base64::engine::general_purpose::STANDARD - .encode(bundle.as_bytes()) - .into(); - (StatusCode::OK, res.dump()) - } - - // ================================================== - // GET USER BY ID - // ================================================== - ["api", "get", "user", id] => { - let id: i64 = id.parse().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - display, - status_msg, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.to_base64().into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - if let Some(display) = display { - res["display"] = display.into(); - } - if let Some(status_msg) = status_msg { - res["status_message"] = status_msg.into(); - } - if let Some(about) = about { - res["about"] = about.into(); - } - if let Some(avatar) = avatar { - res["avatar"] = base64::engine::general_purpose::STANDARD - .encode(avatar) - .into(); - } - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // DIRECT - SHORT LINK RESOLUTION - // ================================================== - ["direct", short @ ..] => { - let short_str = short.join("/"); - let short = short_str.replace("/", ""); - if let Ok(long) = crate::server::short_link::get_short_link(&short).await { - return response - .status(StatusCode::TEMPORARY_REDIRECT) - .header("location", &long); - } else { - return response - .status(StatusCode::TEMPORARY_REDIRECT) - .header("location", "https://tensamin.net"); - } - } - - // ================================================== - // DEFAULT - // ================================================== - _ => { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - }; - + let path_parts: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect(); + if let ["api", "download", "iota_frontend"] = path_parts.as_slice() { + let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); + return match std::fs::read(file_path) { + Ok(bytes) => response + .status(StatusCode::OK) + .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("content-type", "application/zip") + .header( + "content-disposition", + "attachment; filename=\"iota_frontend.zip\"", + ) + .body(Bytes::from(bytes)), + Err(_) => response + .status(StatusCode::NOT_FOUND) + .header("access-control-allow-origin", &crate::config::cors_origin()) + .body(json(&StatusResponse { + status: "error_not_found", + })), + }; + } + if let ["direct", short @ ..] = path_parts.as_slice() { + let short = short.join(""); + let location = crate::server::short_link::get_short_link(&short) + .await + .unwrap_or_else(|_| "https://tensamin.net".to_string()); + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", &location); + } + let (status, body) = route(&path_parts) + .await + .unwrap_or_else(|error| (error.status_code(), error_body(&error))); response .status(status) - .header("access-control-allow-origin", "*") + .header("access-control-allow-origin", &crate::config::cors_origin()) .header("access-control-allow-headers", "*") .header("access-control-allow-methods", "GET, POST, OPTIONS") - .body(body_text) + .body(body) } pub async fn handle_pattern( diff --git a/src/server/middleware.rs b/src/server/middleware.rs new file mode 100644 index 0000000..5f373ec --- /dev/null +++ b/src/server/middleware.rs @@ -0,0 +1,84 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::net::IpAddr; +use std::{collections::VecDeque, time::Instant}; +use tokio::time::interval; + +static REQUESTS: Lazy>> = Lazy::new(DashMap::new); +static CONFIG: Lazy = + Lazy::new(crate::config::RateLimitConfig::from_env); +const MAX_TRACKED_CLIENT_BUCKETS: usize = 100_000; + +pub fn allow(remote_addr: IpAddr, path: &str) -> bool { + let key = if path.contains("register") { + "registration" + } else { + "general" + }; + let map_key = (remote_addr, key.to_string()); + if REQUESTS.len() >= MAX_TRACKED_CLIENT_BUCKETS { + cleanup_expired(); + if REQUESTS.len() >= MAX_TRACKED_CLIENT_BUCKETS && !REQUESTS.contains_key(&map_key) { + return false; + } + } + let limit = if key == "registration" { + CONFIG.registration_requests + } else { + CONFIG.general_requests + }; + let now = Instant::now(); + let mut entries = REQUESTS.entry(map_key).or_default(); + while entries + .front() + .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + { + entries.pop_front(); + } + if entries.len() >= limit { + return false; + } + entries.push_back(now); + true +} + +pub fn spawn_cleanup_task() -> tokio::task::JoinHandle<()> { + tokio::spawn(async { + let mut ticker = interval(CONFIG.window); + loop { + ticker.tick().await; + cleanup_expired(); + } + }) +} + +fn cleanup_expired() { + let now = Instant::now(); + REQUESTS.retain(|_, entries| { + while entries + .front() + .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + { + entries.pop_front(); + } + !entries.is_empty() + }); +} + +#[cfg(test)] +mod tests { + use super::allow; + use std::net::{IpAddr, Ipv4Addr}; + + #[test] + fn tracks_clients_independently() { + let first: IpAddr = "192.0.2.10" + .parse() + .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); + let second: IpAddr = "192.0.2.11" + .parse() + .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); + assert!(allow(first, "/api/get/user/1")); + assert!(allow(second, "/api/get/user/1")); + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 59e9769..2d01c5b 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,4 +1,6 @@ pub mod api; pub mod index; +pub mod middleware; pub mod server; pub mod short_link; +pub mod validation; diff --git a/src/server/short_link.rs b/src/server/short_link.rs index b3f91bd..dd24ebf 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -1,37 +1,27 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::{Rng, thread_rng}; - -static LINKS: Lazy> = Lazy::new(DashMap::new); +use crate::db::short_link_repo; +use rand::RngExt; const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; pub async fn add_short_link(long: &str) -> Result { - let raw = generate_unique_short_link().await; - LINKS.insert(raw.clone(), long.to_string()); - - Ok(format!( - "hmtps://omega.tensamin.net/direct/{}", - format_with_dashes(&raw) - )) -} - -async fn generate_unique_short_link() -> String { loop { - let short = generate_short_link().await; - if !LINKS.contains_key(&short) { - return short; + let raw = generate_short_link().await; + if short_link_repo::insert(&raw, long).await.map_err(|_| ())? { + return Ok(format!( + "https://omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )); } } } pub async fn generate_short_link() -> String { - let len = short_length(); + let len = short_length().await; - let mut rng = thread_rng(); + let mut rng = rand::rng(); (0..len) .map(|_| { - let idx = rng.gen_range(0..CHARSET.len()); + let idx = rng.random_range(0..CHARSET.len()); CHARSET[idx] as char }) .collect() @@ -46,17 +36,17 @@ pub async fn get_short_link(short: &str) -> Result { let frag = short.replace(key, ""); let normalized = normalize_short(&key); - if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { - Ok(format!("{}{}", t, frag)) - } else { - Err(()) - } + let target = short_link_repo::get(&normalized) + .await + .map_err(|_| ())? + .ok_or(())?; + Ok(format!("{}{}", target, frag)) } /* ---------------- helpers ---------------- */ -fn short_length() -> usize { - let count = LINKS.len(); +async fn short_length() -> usize { + let count = short_link_repo::count().await.unwrap_or(0); match count { 0..=1_999 => 4, diff --git a/src/server/validation.rs b/src/server/validation.rs new file mode 100644 index 0000000..b80799c --- /dev/null +++ b/src/server/validation.rs @@ -0,0 +1,42 @@ +use crate::error::{OmegaError, Result}; + +pub fn parse_positive_id(value: &str) -> Result { + let id = value + .parse::() + .map_err(|_| OmegaError::Validation("ID must be a positive integer".to_string()))?; + if id <= 0 { + return Err(OmegaError::Validation( + "ID must be a positive integer".to_string(), + )); + } + Ok(id) +} + +pub fn validate_non_empty(value: &str, field: &str, max_len: usize) -> Result<()> { + let length = value.chars().count(); + if length == 0 || length > max_len { + return Err(OmegaError::Validation(format!( + "{field} must contain 1 to {max_len} characters" + ))); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{parse_positive_id, validate_non_empty}; + + #[test] + fn rejects_invalid_ids() { + assert!(parse_positive_id("bad").is_err()); + assert!(parse_positive_id("0").is_err()); + assert_eq!(parse_positive_id("7").expect("valid test ID"), 7); + } + + #[test] + fn enforces_string_bounds() { + assert!(validate_non_empty("", "username", 15).is_err()); + assert!(validate_non_empty("abcdefghijklmnop", "username", 15).is_err()); + assert!(validate_non_empty("alice", "username", 15).is_ok()); + } +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 91ba841..bec658c 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,3 +1,2 @@ pub mod connection_status; -pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs deleted file mode 100644 index 91cd5ba..0000000 --- a/src/sql/sql.rs +++ /dev/null @@ -1,778 +0,0 @@ -use crate::log; -use mtp::crypto::PublicKeyBundle; -use once_cell::sync::Lazy; -use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::{ - env, - sync::Arc, - time::{SystemTime, UNIX_EPOCH}, -}; -use tokio::sync::RwLock; - -/* -use crate::sql::{ - iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, - sql::{ - change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, - change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, - get_register_id, register_complete_iota, register_complete_user, - }, -}; - */ - -static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); - -pub async fn connect() -> Result, sqlx::Error> { - let url = env::var("DB_URL").expect("DB_URL is not set"); - - MySqlPoolOptions::new() - .max_connections(200) - .connect(&url) - .await -} -// Omega -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -pub async fn initialize_db() -> Result<(), sqlx::Error> { - let pool = connect().await?; - let mut db_lock = SQL_DB.write().await; - // create tables - // with indexes - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - users ( - id BIGINT NOT NULL PRIMARY KEY, - username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, - display VARCHAR(15) COLLATE utf8mb4_bin, - status VARCHAR(15) COLLATE utf8mb4_bin, - about VARCHAR(200) COLLATE utf8mb4_bin, - avatar MEDIUMBLOB, - sub_level INT(11) NOT NULL DEFAULT 0, - sub_end BIGINT(20) NOT NULL DEFAULT 0, - public_key BLOB NOT NULL, - private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', - iota_id BIGINT NOT NULL, - token VARCHAR(256) NOT NULL UNIQUE COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - iotas ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - omikrons ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL, - location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - port INT(11) NOT NULL DEFAULT 959 - )", - ) - .execute(&pool) - .await; - // Retrofits `port` onto omikrons tables created before this column existed; - // `CREATE TABLE IF NOT EXISTS` above is a no-op against an already-existing table. - let _ = sqlx::query( - "ALTER TABLE omikrons ADD COLUMN IF NOT EXISTS port INT(11) NOT NULL DEFAULT 959", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - notifications ( - id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, - sender_id BIGINT NOT NULL, - receiver_id BIGINT NOT NULL, - amount BIGINT NOT NULL DEFAULT 0 - )", - ) - .execute(&pool) - .await; - *db_lock = Some(pool); - Ok(()) -} - -// ========================================================================================== -// REGISTER -// ========================================================================================== -pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); -pub static CURRENT_REGISTER_PROCESS: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); - -pub async fn get_register_id() -> u64 { - let mut current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as u64; - - loop { - let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); - - if current_locked < current_time { - let result = CURRENT_MILLI_USED.compare_exchange( - current_locked, // expected value - current_time, // new value - Ordering::SeqCst, // acquire/release ordering - Ordering::SeqCst, // failure ordering - ); - - match result { - Ok(_) => { - CURRENT_REGISTER_PROCESS.write().await.push(current_time); - return current_time; - } - Err(_) => { - continue; - } - } - } else { - current_time = current_locked + 1; - } - } -} - -// ========================================================================================== -// USERS -// ========================================================================================== - -pub async fn get_by_username( - username: &str, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", - ) - .bind(username) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - let private_key_hash: Vec = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - String::from_utf8_lossy(&private_key_hash).to_string(), - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_by_user_id( - id: i64, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - let private_key_hash: Vec = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - String::from_utf8_lossy(&private_key_hash).to_string(), - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_users_by_iota_id( - iota_id_param: i64, -) -> Result< - Vec<( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - )>, - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", - ) - .bind(iota_id_param) - .fetch_all(&pool) - .await?; - - let mut users = Vec::new(); - for row in rows { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - let private_key_hash: Vec = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - users.push(( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - String::from_utf8_lossy(&private_key_hash).to_string(), - String::from_utf8_lossy(&token).to_string(), - )); - } - - Ok(users) -} - -pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET username = ? WHERE id = ?") - .bind(new_username.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET display = ? WHERE id = ?") - .bind(new_display.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") - .bind(new_avatar.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET about = ? WHERE id = ?") - .bind(new_about.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET status = ? WHERE id = ?") - .bind(new_status.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM users WHERE id = ?") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") - .bind(new_iota_id) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_keys( - id: i64, - new_public_key: PublicKeyBundle, - new_private_key_hash: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key.as_bytes()) - .bind(new_private_key_hash.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET token = ? WHERE id = ?") - .bind(new_token.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn register_complete_user( - id: i64, - username: String, - public_key: PublicKeyBundle, - iota_id: i64, - token: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query( - "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", - ) - .bind(id) - .bind(username.as_bytes().to_vec()) - .bind(public_key.as_bytes()) - .bind(iota_id) - .bind(token.as_bytes().to_vec()) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn print_users() -> Result<(), Box> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - log!("Printing users..."); - for row in sqlx::query( - "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", - ) - .fetch_all(&pool) - .await? - .iter() - { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - - log!( - "User: {:?}", - ( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), - status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), - about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), - sub_level, - sub_end - ) - ); - } - - Ok(()) -} - -// ========================================================================================== -// IOTA -// ========================================================================================== -pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { - let new_id = get_register_id().await as i64; - register_complete_iota(new_id, public_key).await?; - Ok(new_id) -} - -pub async fn register_complete_iota( - id: i64, - public_key: PublicKeyBundle, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") - .bind(id) - .bind(public_key.as_bytes()) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn get_iota_by_id(id: i64) -> Result<(i64, PublicKeyBundle), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let result = - sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") - .bind(id) - .fetch_optional(&pool) - .await; - - match result { - Ok(optional_row) => match optional_row { - Some((id_i64, public_key)) => { - let bundle = PublicKeyBundle::from_bytes(&public_key) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - Ok((id_i64, bundle)) - } - _ => Err(sqlx::Error::RowNotFound), - }, - Err(e) => Err(e), - } -} - -pub async fn change_iota_key(id: i64, new_key: PublicKeyBundle) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(new_key.as_bytes()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM iotas WHERE id = ?") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -// ========================================================================================== -// OMIKRONS -// ========================================================================================== - -pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String, u16), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query_as::<_, (Vec, Vec, i32)>( - "SELECT public_key, ip_address, port FROM omikrons WHERE id = ?", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some((public_key, ip_address, port)) => { - let bundle = PublicKeyBundle::from_bytes(&public_key) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - Ok(( - bundle, - String::from_utf8_lossy(&ip_address).to_string(), - port as u16, - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -// ========================================================================================== -// PHI -// ========================================================================================== - -pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - INSERT INTO notifications (sender_id, receiver_id, amount) - VALUES (?, ?, 1) - ON DUPLICATE KEY UPDATE amount = amount + 1 - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query_as::<_, (i64, i64)>( - r#" - SELECT sender_id, amount FROM notifications WHERE receiver_id = ? - "#, - ) - .bind(user_id) - .fetch_all(pool) - .await -} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 288fcdc..a0c2cd8 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,4 +1,5 @@ -use crate::sql; +use crate::db::user_repo; +use crate::models::IotaId; use crate::sql::connection_status::UserStatus; use dashmap::DashMap; use once_cell::sync::Lazy; @@ -33,27 +34,30 @@ pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { } pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { - let mut vec = r.value().clone(); - vec.retain(|&id| id != omikron_id); - let empty = vec.is_empty(); - drop(r); - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); - empty - } else { - false - }; - - if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { - let primary_id = *primary_ref.value(); - drop(primary_ref); - if primary_id == omikron_id { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } + let mut replacement = None; + let mut connections_empty = false; + if let Some(mut entry) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { + entry.retain(|&id| id != omikron_id); + connections_empty = entry.is_empty(); + replacement = entry.first().copied(); } if connections_empty { - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + IOTA_OMIKRON_CONNECTIONS.remove_if(&iota_id, |_, connections| connections.is_empty()); + } + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(&iota_id) + .is_some_and(|primary| *primary == omikron_id) + { + match replacement { + Some(omikron_id) => { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); + } + None => { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + } } connections_empty @@ -87,9 +91,9 @@ pub async fn get_all_connections() let iota_id = *entry.key(); let omikron_ids = entry.value().clone(); - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { for user in users { - let user_id = user.0 as i64; + let user_id = user.id.0; if let Some(conn) = USER_STATUS_MAP.get(&user_id) { let user_omikron_id = conn.omikron_id; if omikron_ids.contains(&user_omikron_id) { @@ -117,6 +121,12 @@ pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { ); } +pub fn untrack_user_status(user_id: i64, omikron_id: i64) { + USER_STATUS_MAP.remove_if(&user_id, |_, connection| { + connection.omikron_id == omikron_id + }); +} + pub fn get_user_status(user_id: i64) -> Option { USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } @@ -128,57 +138,38 @@ pub fn untrack_many_users(user_ids: &[i64]) { } pub async fn untrack_omikron(omikron_id: i64) { - let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION - .iter() - .filter(|entry| *entry.value() == omikron_id) - .map(|entry| *entry.key()) - .collect(); - - for key in primary_keys_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); - } - let mut offline_iotas = Vec::new(); - let mut primary_to_remove = Vec::new(); + let mut primary_replacements = Vec::new(); - // Collect iotas and primary info first - for r in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *r.key(); - let mut connections = r.value().clone(); - connections.retain(|&id| id != omikron_id); - - if connections.is_empty() { + for mut entry in IOTA_OMIKRON_CONNECTIONS.iter_mut() { + let iota_id = *entry.key(); + entry.retain(|&id| id != omikron_id); + if entry.is_empty() { offline_iotas.push(iota_id); - } - - if IOTA_PRIMARY_OMIKRON_CONNECTION + } else if IOTA_PRIMARY_OMIKRON_CONNECTION .get(&iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) + .is_some_and(|primary| *primary == omikron_id) { - primary_to_remove.push(iota_id); + primary_replacements.push((iota_id, entry[0])); } - - // Update the connections vector after filtering - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); } - // Step 2: Remove primary connections safely - for iota_id in primary_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + for iota_id in &offline_iotas { + IOTA_OMIKRON_CONNECTIONS.remove_if(iota_id, |_, connections| connections.is_empty()); + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); + } + + for (iota_id, replacement) in primary_replacements { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, replacement); } - // Step 3: Remove users that were on this omikron USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP for iota_id in offline_iotas { - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { for user in users { - USER_STATUS_MAP.remove(&(user.0 as i64)); + USER_STATUS_MAP.remove(&user.id.0); } } - // Finally remove the empty connections vector - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); } } diff --git a/src/transport/connection.rs b/src/transport/connection.rs new file mode 100644 index 0000000..eff0bb1 --- /dev/null +++ b/src/transport/connection.rs @@ -0,0 +1 @@ +pub(crate) use super::omikron_connection::OmikronConnection; diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs new file mode 100644 index 0000000..1927b14 --- /dev/null +++ b/src/transport/handlers/account.rs @@ -0,0 +1,43 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::{iota_repo, user_repo}, + models::{IotaId, UserId}, +}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use std::sync::Arc; + +async fn delete( + connection: Arc, + value: CommunicationValue, + result: impl std::future::Future>, +) -> OmikronResult<()> { + let response = match result.await { + Ok(()) => CommunicationValue::new(CommunicationType::Success), + Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + }; + connection.send(&response.with_id(value.get_id())).await +} + +pub async fn user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + delete( + connection, + value.clone(), + user_repo::delete_user(UserId::from(value.get_sender() as i64)), + ) + .await +} +pub async fn iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + delete( + connection, + value.clone(), + iota_repo::delete_iota(IotaId::from(value.get_sender() as i64)), + ) + .await +} diff --git a/src/transport/handlers/calls.rs b/src/transport/handlers/calls.rs new file mode 100644 index 0000000..9b16182 --- /dev/null +++ b/src/transport/handlers/calls.rs @@ -0,0 +1 @@ +/* Call and WebRTC commands are reserved for the transport extensions that define those protocol values. */ diff --git a/src/transport/handlers/links.rs b/src/transport/handlers/links.rs new file mode 100644 index 0000000..a43cb99 --- /dev/null +++ b/src/transport/handlers/links.rs @@ -0,0 +1,21 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::server::short_link::add_short_link; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use std::sync::Arc; + +pub async fn shorten( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let link = value + .get_data(DataType::Link) + .as_str() + .ok_or(crate::error::OmegaError::InvalidResponse)?; + let short = add_short_link(link) + .await + .map_err(|_| crate::error::OmegaError::Transport("short link error".to_string()))?; + let response = CommunicationValue::new(CommunicationType::ShortenLink) + .with_id(value.get_id()) + .add_typed_default(DataType::Link, DataValue::Str(short)); + connection.send(&response).await +} diff --git a/src/transport/handlers/messaging.rs b/src/transport/handlers/messaging.rs new file mode 100644 index 0000000..38c66d6 --- /dev/null +++ b/src/transport/handlers/messaging.rs @@ -0,0 +1 @@ +/* Message delivery remains in the connection dispatcher until the protocol exposes a separate message handler contract. */ diff --git a/src/transport/handlers/mod.rs b/src/transport/handlers/mod.rs new file mode 100644 index 0000000..cb506cf --- /dev/null +++ b/src/transport/handlers/mod.rs @@ -0,0 +1,9 @@ +pub mod account; +pub mod calls; +pub mod links; +pub mod messaging; +pub mod notifications; +pub mod presence; +pub mod register; +pub mod states; +pub mod user_data; diff --git a/src/transport/handlers/notifications.rs b/src/transport/handlers/notifications.rs new file mode 100644 index 0000000..5f23474 --- /dev/null +++ b/src/transport/handlers/notifications.rs @@ -0,0 +1,116 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{db::notification_repo, log, models::UserId}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::type_map::TypeMap; +use std::sync::Arc; + +pub async fn get( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let notifications = + match notification_repo::get_notifications(UserId::from(value.get_sender() as i64)).await { + Ok(items) => items + .into_iter() + .map(|item| { + let tm = TypeMap::latest(); + let Some(sender) = DataType::SenderId.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + let Some(amount) = DataType::Amount.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + DataValue::Container(vec![ + (sender, DataValue::SignedNumber(item.sender_id.0.into())), + (amount, DataValue::SignedNumber(item.amount.into())), + ]) + }) + .collect(), + Err(error) => { + log!( + crate::util::logger::PrintType::General, + "SQL get_notifications error: {}", + error + ); + Vec::new() + } + }; + let response = CommunicationValue::new(CommunicationType::GetNotifications) + .with_id(value.get_id()) + .add_typed_default(DataType::Notifications, DataValue::Array(notifications)); + connection.send(&response).await +} + +pub async fn read( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let receiver = match value.get_sender() { + sender if sender > 0 => sender as i64, + _ => match value.get_data(DataType::ReceiverId).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + let Some(other) = value + .get_data(DataType::SenderId) + .as_number() + .map(|id| id as i64) + else { + return Ok(()); + }; + if let Err(error) = + notification_repo::read_notification(UserId::from(receiver), UserId::from(other)).await + { + log!( + crate::util::logger::PrintType::General, + "SQL read_notification error: {}", + error + ); + } else { + let response = + CommunicationValue::new(CommunicationType::ReadNotification).with_id(value.get_id()); + let _ = connection.send(&response).await; + let sync = CommunicationValue::new(CommunicationType::ReadNotification) + .with_receiver(receiver as u64) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(other.into())); + crate::transport::omikron_manager::send_to_user(receiver, &sync).await; + } + Ok(()) +} + +pub async fn push( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let receiver = match value.get_receiver() { + receiver if receiver > 0 => receiver as i64, + _ => match value.get_data(DataType::ReceiverId).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + let sender = value + .get_data(DataType::SenderId) + .as_number() + .map(|id| id as i64) + .unwrap_or(value.get_sender() as i64); + if let Err(error) = + notification_repo::add_notification(UserId::from(receiver), UserId::from(sender)).await + { + log!( + crate::util::logger::PrintType::General, + "SQL add_notification error: {}", + error + ); + } else { + let response = + CommunicationValue::new(CommunicationType::PushNotification).with_id(value.get_id()); + let _ = connection.send(&response).await; + let push = CommunicationValue::new(CommunicationType::PushNotification) + .with_receiver(receiver as u64) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender.into())); + crate::transport::omikron_manager::send_to_user(receiver, &push).await; + } + Ok(()) +} diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs new file mode 100644 index 0000000..1dd10d0 --- /dev/null +++ b/src/transport/handlers/presence.rs @@ -0,0 +1,128 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::user_repo, + log_in, + models::IotaId, + sql::{connection_status::UserStatus, user_online_tracker}, +}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use std::sync::Arc; + +pub async fn user_connected( + _connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User connected"); + if let Some(user_id) = value.get_data(DataType::UserId).as_number() { + let status = value + .get_data(DataType::UserState) + .as_str() + .and_then(UserStatus::from_str) + .unwrap_or(UserStatus::user_online); + if let Ok(user_id) = i64::try_from(user_id) { + user_online_tracker::track_user_status(user_id, status, omikron_id); + } + } + Ok(()) +} + +pub async fn user_disconnected( + _: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); + if let Some(user_id) = value.get_data(DataType::UserId).as_number() { + user_online_tracker::untrack_user_status(user_id as i64, omikron_id); + } + Ok(()) +} + +pub async fn iota_connected( + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "IOTA connected"); + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .map(|id| id as i64) + else { + return Ok(()); + }; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + let mut user_ids = Vec::new(); + match user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { + Ok(users) => { + for user in users { + user_ids.push(DataValue::SignedNumber(user.id.0.into())); + user_online_tracker::track_user_status( + user.id.0, + UserStatus::user_offline, + omikron_id, + ); + } + } + Err(_) => log_in!( + crate::util::logger::PrintType::General, + "SQL error loading users for IOTA" + ), + } + let response = CommunicationValue::new(CommunicationType::IotaUserData) + .with_id(value.get_id()) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); + let _ = connection.send(&response).await; + Ok(()) +} + +pub async fn iota_disconnected( + _: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "IOTA disconnected"); + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .map(|id| id as i64) + else { + return Ok(()); + }; + if user_online_tracker::untrack_iota_connection(iota_id, omikron_id) { + if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { + user_online_tracker::untrack_many_users( + &users.iter().map(|user| user.id.0).collect::>(), + ); + } + } + Ok(()) +} + +pub async fn sync_status( + _: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + if let DataValue::Array(ids) = value.get_data(DataType::UserIds) { + for id in ids { + if let DataValue::SignedNumber(id) = id { + user_online_tracker::track_user_status( + *id as i64, + UserStatus::user_offline, + omikron_id, + ); + } + } + } + if let DataValue::Array(ids) = value.get_data(DataType::IotaIds) { + for id in ids { + if let DataValue::SignedNumber(id) = id { + user_online_tracker::track_iota_connection(*id as i64, omikron_id, true); + } + } + } + Ok(()) +} diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs new file mode 100644 index 0000000..23bf3e7 --- /dev/null +++ b/src/transport/handlers/register.rs @@ -0,0 +1,151 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::{iota_repo, user_repo}, + models::{IotaId, UserId}, +}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::PublicKeyBundle, +}; +use std::sync::Arc; + +pub async fn get_register( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let register_id = user_repo::get_register_id().await?; + let response = CommunicationValue::new(CommunicationType::GetRegister) + .with_id(value.get_id()) + .add_typed_default( + DataType::UserId, + DataValue::SignedNumber(register_id.0.into()), + ); + connection.send(&response).await +} + +pub async fn complete_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let iota_id = value + .get_data(DataType::IotaId) + .as_number() + .map(|id| id as i64); + let public_key = value + .get_data(DataType::PublicKey) + .as_str() + .and_then(|key| PublicKeyBundle::from_base64(key).ok()); + let Some(public_key) = public_key else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + match iota_id { + Some(iota_id) => { + match iota_repo::register_complete_iota(IotaId::from(iota_id), public_key).await { + Ok(()) => { + connection + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.get_id()), + ) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default( + DataType::ErrorType, + DataValue::Str(error.to_string()), + ), + ) + .await + } + } + } + None => match iota_repo::create_new_iota(public_key).await { + Ok(id) => { + connection + .send( + &CommunicationValue::new(CommunicationType::CompleteRegisterIota) + .with_id(value.get_id()) + .add_typed_default( + DataType::IotaId, + DataValue::SignedNumber(id.0.into()), + ), + ) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default( + DataType::ErrorType, + DataValue::Str(error.to_string()), + ), + ) + .await + } + }, + } +} + +pub async fn complete_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let user_id = value + .get_data(DataType::UserId) + .as_number() + .map(|id| id as i64); + let username = value + .get_data(DataType::Username) + .as_str() + .map(str::to_owned); + let public_key = value + .get_data(DataType::PublicKey) + .as_str() + .and_then(|key| PublicKeyBundle::from_base64(key).ok()); + let reset_token = value + .get_data(DataType::ResetToken) + .as_str() + .map(str::to_owned); + let Some((user_id, username, public_key, reset_token)) = user_id + .zip(username) + .zip(public_key) + .zip(reset_token) + .map(|(((id, name), key), token)| (id, name, key, token)) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + match user_repo::register_complete_user( + UserId::from(user_id), + username, + public_key, + IotaId::from(value.get_sender() as i64), + reset_token, + ) + .await + { + Ok(()) => { + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await + } + } +} diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs new file mode 100644 index 0000000..be116b1 --- /dev/null +++ b/src/transport/handlers/states.rs @@ -0,0 +1,46 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::sql::{connection_status::UserStatus, user_online_tracker}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + type_map::TypeMap, +}; +use std::sync::Arc; + +pub async fn get( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let DataValue::Array(ids) = value.get_data(DataType::UserIds) else { + return Ok(()); + }; + let tm = TypeMap::latest(); + let states = ids + .iter() + .filter_map(|id| { + let DataValue::SignedNumber(id) = id else { + return None; + }; + let status = user_online_tracker::get_user_status(*id as i64) + .map(|status| { + if status.connection_type == UserStatus::user_invisible { + UserStatus::user_offline.to_string() + } else { + status.connection_type.to_string() + } + }) + .unwrap_or_else(|| UserStatus::iota_offline.to_string()); + let mut map = Vec::new(); + if let Some(kind) = DataType::UserId.try_to_id(&tm) { + map.push((kind, DataValue::SignedNumber((*id as i64).into()))); + } + if let Some(kind) = DataType::UserState.try_to_id(&tm) { + map.push((kind, DataValue::Str(status))); + } + Some(DataValue::Container(map)) + }) + .collect(); + let response = CommunicationValue::new(CommunicationType::GetStates) + .with_id(value.get_id()) + .add_typed_default(DataType::UserStates, DataValue::Array(states)); + connection.send(&response).await +} diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs new file mode 100644 index 0000000..27c9dbb --- /dev/null +++ b/src/transport/handlers/user_data.rs @@ -0,0 +1,271 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::{iota_repo, user_repo}, + models::{IotaId, UserId}, + sql::{connection_status::UserStatus, user_online_tracker}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::PublicKeyBundle, +}; +use std::sync::Arc; + +fn connections(iota_id: i64) -> DataValue { + DataValue::Array( + user_online_tracker::get_iota_omikron_connections(iota_id) + .unwrap_or_default() + .into_iter() + .map(|id| DataValue::SignedNumber(id.into())) + .collect(), + ) +} + +pub async fn get_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let user = if let Some(id) = value.get_data(DataType::UserId).as_number() { + user_repo::get_by_user_id(UserId::from(id as i64)) + .await + .ok() + } else if let Some(name) = value.get_data(DataType::Username).as_str() { + user_repo::get_by_username(name).await.ok() + } else { + None + }; + let Some(user) = user else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; + let id = user.id.0; + let iota_id = user.iota_id.0; + let username = user.username.clone(); + let display = user + .display + .filter(|name| !name.is_empty()) + .unwrap_or_else(|| username.clone()); + let mut response = CommunicationValue::new(CommunicationType::GetUserData) + .with_id(value.get_id()) + .add_typed_default(DataType::Username, DataValue::Str(username)) + .add_typed_default( + DataType::PublicKey, + DataValue::Str(user.public_key.to_base64()), + ) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::Display, DataValue::Str(display)) + .add_typed_default( + DataType::SubLevel, + DataValue::SignedNumber(user.sub_level as i128), + ) + .add_typed_default( + DataType::SubEnd, + DataValue::SignedNumber(user.sub_end.into()), + ); + if let Some(status) = user.status.filter(|value| !value.is_empty()) { + response = response.add_typed_default(DataType::Status, DataValue::Str(status)); + } + if let Some(about) = user.about.filter(|value| !value.is_empty()) { + response = response.add_typed_default(DataType::About, DataValue::Str(about)); + } + if let Some(avatar) = user.avatar { + response = + response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar))); + } + let online = user_online_tracker::get_user_status(id); + response = response + .add_typed_default( + DataType::OnlineStatus, + DataValue::Str( + online + .as_ref() + .map(|status| { + if status.connection_type == UserStatus::user_invisible { + UserStatus::user_offline.to_string() + } else { + status.connection_type.to_string() + } + }) + .unwrap_or_else(|| UserStatus::iota_offline.to_string()), + ), + ) + .add_typed_default(DataType::OmikronConnections, connections(iota_id)); + if let Some(status) = online { + response = response.add_typed_default( + DataType::OmikronId, + DataValue::SignedNumber(status.omikron_id.into()), + ); + } + connection.send(&response).await +} + +pub async fn get_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let found = if let Some(id) = value.get_data(DataType::IotaId).as_number() { + iota_repo::get_iota_by_id(IotaId::from(id as i64)) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, None, None)) + } else if let Some(id) = value.get_data(DataType::UserId).as_number() { + if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await { + iota_repo::get_iota_by_id(user.iota_id) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)) + } else { + None + } + } else if let Some(name) = value.get_data(DataType::Username).as_str() { + if let Ok(user) = user_repo::get_by_username(name).await { + iota_repo::get_iota_by_id(user.iota_id) + .await + .ok() + .map(|iota| { + ( + iota.id.0, + iota.public_key, + Some(user.id.0), + Some(name.to_owned()), + ) + }) + } else { + None + } + } else { + None + }; + let Some((id, key, user_id, username)) = found else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; + let mut response = CommunicationValue::new(CommunicationType::GetIotaData) + .with_id(value.get_id()) + .add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())) + .add_typed_default(DataType::OmikronConnections, connections(id)); + if let Some(user_id) = user_id { + response = + response.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())); + } + if let Some(username) = username { + response = response.add_typed_default(DataType::Username, DataValue::Str(username)); + } + connection.send(&response).await +} + +async fn update_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let id = UserId::from(value.get_sender() as i64); + let mut error = None; + if let Some(name) = value.get_data(DataType::Username).as_str() { + error = user_repo::change_username(id, name.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + if error.is_none() { + if let Some(name) = value.get_data(DataType::Display).as_str() { + error = user_repo::change_display_name(id, name.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(avatar) = value.get_data(DataType::Avatar).as_str() { + error = user_repo::change_avatar(id, avatar.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(about) = value.get_data(DataType::About).as_str() { + error = user_repo::change_about(id, about.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(status) = value.get_data(DataType::Status).as_str() { + error = user_repo::change_status(id, status.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(key) = value + .get_data(DataType::PublicKey) + .as_str() + .and_then(|key| PublicKeyBundle::from_base64(key).ok()) + { + error = user_repo::change_keys(id, key) + .await + .err() + .map(|e| e.to_string()); + } + } + let response = match error { + None => CommunicationValue::new(CommunicationType::Success), + Some(error) => CommunicationValue::new(CommunicationType::ErrorInternal) + .add_typed_default(DataType::ErrorType, DataValue::Str(error)), + }; + connection.send(&response.with_id(value.get_id())).await +} + +pub async fn change_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + update_user(connection, value).await +} + +pub async fn change_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(reset) = value.get_data(DataType::ResetToken).as_str() else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(new_token) = value.get_data(DataType::NewToken).as_str() else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let user_id = UserId::from(value.get_sender() as i64); + let user = match user_repo::get_by_user_id(user_id).await { + Ok(user) => user, + Err(_) => { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + } + }; + if user.token != reset { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + } + let result = + match user_repo::change_iota_id(user_id, IotaId::from(value.get_sender() as i64)).await { + Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, + Err(error) => Err(error), + }; + let response = match result { + Ok(()) => CommunicationValue::new(CommunicationType::Success), + Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + }; + connection.send(&response.with_id(value.get_id())).await +} diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 4f3953a..8171bf2 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,2 +1,4 @@ +pub mod connection; +pub mod handlers; pub mod omikron_connection; pub mod omikron_manager; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 6a5517e..b2b01b3 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,23 +1,22 @@ +use crate::models::OmikronId; use crate::{ - load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, - server::{self, short_link::add_short_link}, - sql::{ - connection_status::UserStatus, - sql::{self, get_by_user_id, get_by_username, get_iota_by_id}, - user_online_tracker::{self}, - }, + load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server, transport::omikron_manager, util::{file_util::load_file_vec, logger::PrintType}, }; -use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use mtp::host::{AuthenticationPolicy, HostConfig, Policy, SendMode}; -use mtp::webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}; -use mtp::crypto::PublicKeyBundle; -use std::net::{IpAddr, Ipv4Addr}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::PublicKeyBundle, + host::{AuthenticationPolicy, HostConfig, Policy, SendMode}, + webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}, +}; use std::{ - sync::Arc, + net::{IpAddr, Ipv4Addr}, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, time::{Duration, Instant}, }; use tokio::{ @@ -27,25 +26,24 @@ use tokio::{ const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); +static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0); +static ACTIVE_CONNECTIONS_BY_IP: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(DashMap::new); -#[derive(Debug, thiserror::Error)] -pub enum OmikronError { - #[error("Not connected")] - NotConnected, - #[error("Not authenticated")] - NotAuthenticated, - #[error("Invalid response")] - InvalidResponse, - #[error("Authentication failed")] - AuthenticationFailed, - #[error("SQL error: {0}")] - Sql(String), - #[error("Send error: {0}")] - Send(String), +struct ConnectionLimitGuard(Option); +impl Drop for ConnectionLimitGuard { + fn drop(&mut self) { + ACTIVE_CONNECTIONS.fetch_sub(1, Ordering::AcqRel); + if let Some(ip) = self.0 { + if let Some(mut count) = ACTIVE_CONNECTIONS_BY_IP.get_mut(&ip) { + *count = count.saturating_sub(1); + } + ACTIVE_CONNECTIONS_BY_IP.remove_if(&ip, |_, count| *count == 0); + } + } } -pub type OmikronResult = Result; - +pub type OmikronResult = crate::error::Result; pub struct WaitingTask { pub task: Box, CommunicationValue) -> bool + Send + Sync>, pub inserted_at: Instant, @@ -58,7 +56,6 @@ pub struct OmikronConnection { waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, } - impl Drop for OmikronConnection { fn drop(&mut self) { if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { @@ -69,15 +66,13 @@ impl Drop for OmikronConnection { impl OmikronConnection { pub fn new(sender: WebMtpSender, id: u64) -> Arc { - let conn = Arc::new(Self { + Arc::new(Self { id, sender: Mutex::new(Some(sender)), ping: RwLock::new(-1), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), - }); - - conn + }) } pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { @@ -86,28 +81,24 @@ impl OmikronConnection { PrintType::Omega, "Omikron connection started" ); - let cleanup_conn = self.clone(); - let cleanup_handle = tokio::spawn(async move { + *self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); loop { ticker.tick().await; cleanup_conn .waiting_tasks - .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + .retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE); } - }); - *self.cleanup_handle.lock().unwrap() = Some(cleanup_handle); - - while let Ok(cv) = receiver.receive().await { - if let Err(e) = self.clone().process_message(cv).await { - log_err!(0, PrintType::Omega, "Error processing message: {}", e); - if matches!(e, OmikronError::NotConnected) { + })); + while let Ok(value) = receiver.receive().await { + if let Err(error) = self.clone().process_message(value).await { + log_err!(0, PrintType::Omega, "Error processing message: {}", error); + if matches!(error, crate::error::OmegaError::NotConnected) { break; } } } - self.clone().cleanup().await; log_in!( self.id as i64, @@ -116,845 +107,120 @@ impl OmikronConnection { ); } - async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { - log_cv_in!(PrintType::Omikron, &cv); + async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { + if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { + log_cv_in!(PrintType::Omikron, &value); } - - let msg_id = cv.get_id(); - - if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { - let _ = (task.task)(self.clone(), cv); + if let Some((_, task)) = self.waiting_tasks.remove(&value.get_id()) { + let _ = (task.task)(self.clone(), value); return Ok(()); } - - if cv.is_type(CommunicationType::Ping) { - return self.handle_ping(cv).await; + if value.is_type(CommunicationType::Ping) { + return self.ping(value).await; } - - let omikron_id = self.id as i64; - self.clone().handle_authenticated(cv, omikron_id).await + self.dispatch(value).await } - async fn handle_authenticated( - self: Arc, - cv: CommunicationValue, - omikron_id: i64, - ) -> OmikronResult<()> { - let comm_type = cv.get_comm_type_enum(); - match comm_type { - Some(CommunicationType::ShortenLink) => self.handle_shorten_link(cv).await, - + async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { + let id = self.id as i64; + match value.get_comm_type_enum() { + Some(CommunicationType::ShortenLink) => { + crate::transport::handlers::links::shorten(self, value).await + } Some(CommunicationType::UserConnected) => { - self.handle_user_connected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::user_connected(self, value, id).await } Some(CommunicationType::UserDisconnected) => { - self.handle_user_disconnected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::user_disconnected(self, value, id).await } Some(CommunicationType::IotaConnected) => { - self.handle_iota_connected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::iota_connected(self, value, id).await } Some(CommunicationType::IotaDisconnected) => { - self.handle_iota_disconnected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::iota_disconnected(self, value, id).await } Some(CommunicationType::SyncClientIotaStatus) => { - self.handle_sync_status(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::sync_status(self, value, id).await + } + Some(CommunicationType::GetUserData) => { + crate::transport::handlers::user_data::get_user(self, value).await + } + Some(CommunicationType::GetIotaData) => { + crate::transport::handlers::user_data::get_iota(self, value).await + } + Some(CommunicationType::ChangeUserData) => { + crate::transport::handlers::user_data::change_user(self, value).await + } + Some(CommunicationType::ChangeIotaData) => { + crate::transport::handlers::user_data::change_iota(self, value).await + } + Some(CommunicationType::GetRegister) => { + crate::transport::handlers::register::get_register(self, value).await } - - Some(CommunicationType::GetUserData) => self.handle_get_user_data(cv).await, - Some(CommunicationType::GetIotaData) => self.handle_get_iota_data(cv).await, - - Some(CommunicationType::GetRegister) => self.handle_get_register(cv).await, Some(CommunicationType::CompleteRegisterIota) => { - self.handle_complete_register_iota(cv).await + crate::transport::handlers::register::complete_iota(self, value).await } Some(CommunicationType::CompleteRegisterUser) => { - self.handle_complete_register_user(cv).await + crate::transport::handlers::register::complete_user(self, value).await + } + Some(CommunicationType::DeleteUser) => { + crate::transport::handlers::account::user(self, value).await + } + Some(CommunicationType::DeleteIota) => { + crate::transport::handlers::account::iota(self, value).await + } + Some(CommunicationType::GetNotifications) => { + crate::transport::handlers::notifications::get(self, value).await + } + Some(CommunicationType::ReadNotification) => { + crate::transport::handlers::notifications::read(self, value).await + } + Some(CommunicationType::PushNotification) => { + crate::transport::handlers::notifications::push(self, value).await + } + Some(CommunicationType::GetStates) => { + crate::transport::handlers::states::get(self, value).await } - - Some(CommunicationType::ChangeUserData) => self.handle_change_user_data(cv).await, - Some(CommunicationType::ChangeIotaData) => self.handle_change_iota_data(cv).await, - Some(CommunicationType::DeleteUser) => self.handle_delete_user(cv).await, - Some(CommunicationType::DeleteIota) => self.handle_delete_iota(cv).await, - - Some(CommunicationType::GetNotifications) => self.handle_get_notifications(cv).await, - Some(CommunicationType::ReadNotification) => self.handle_read_notification(cv).await, - Some(CommunicationType::PushNotification) => self.handle_push_notification(cv).await, - Some(CommunicationType::GetStates) => self.handle_get_states(cv).await, - _ => { log_err!( 0, PrintType::Omega, "Unknown message type: {:?}", - cv.get_type() + value.get_type() ); Ok(()) } } } - async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let link = cv - .get_data(DataType::Link) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let short = add_short_link(link) - .await - .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; - - let response = CommunicationValue::new(CommunicationType::ShortenLink) - .with_id(cv.get_id()) - .add_typed_default(DataType::Link, DataValue::Str(short)); - - self.send(&response).await - } - - async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - let status = cv - .get_data(DataType::UserState) - .as_str() - .and_then(|s| UserStatus::from_str(s)) - .unwrap_or(UserStatus::user_online); - user_online_tracker::track_user_status(user_id.try_into().unwrap(), status, omikron_id); - } - } - - async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { - log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { - user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_offline, - status.omikron_id, - ); - } - } - } - - async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA connected"); - let iota_id = match cv.get_data(DataType::IotaId).as_number() { - Some(id) => id as i64, - None => return, - }; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - - let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { - for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::SignedNumber(user_id.try_into().unwrap())); - user_online_tracker::track_user_status( - user_id.try_into().unwrap(), - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error loading users for IOTA"); - } - - let response = CommunicationValue::new(CommunicationType::IotaUserData) - .with_id(cv.get_id()) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); - - let _ = self.send(&response).await; - } - - async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA disconnected"); - let iota_id = match cv.get_data(DataType::IotaId).as_number() { - Some(id) => id as i64, - None => return, - }; - let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { - let user_ids: Vec = users.iter().map(|u| u.0.try_into().unwrap()).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } - } - } - - async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { - if let DataValue::Array(user_ids) = cv.get_data(DataType::UserIds) { - for user_id_val in user_ids { - if let DataValue::SignedNumber(user_id) = user_id_val { - user_online_tracker::track_user_status( - *user_id as i64, - UserStatus::user_offline, - omikron_id, - ); - } - } - } - - if let DataValue::Array(iota_ids) = cv.get_data(DataType::IotaIds) { - for iota_id_val in iota_ids { - if let DataValue::SignedNumber(iota_id) = iota_id_val { - user_online_tracker::track_iota_connection(*iota_id as i64, omikron_id, true); - } - } - } - } - - async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - if let Ok(user_data) = get_by_user_id(user_id as i64).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - if let Some(username) = cv.get_data(DataType::Username).as_str() { - if let Ok(user_data) = get_by_username(username).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - let response = - CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_user_data_response( - self: Arc, - msg_id: u32, - user: ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - ), - ) -> CommunicationValue { - let ( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - ) = user; - - let mut response = CommunicationValue::new(CommunicationType::GetUserData) - .with_id(msg_id) - .add_typed_default(DataType::Username, DataValue::Str(username.clone())) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default( - DataType::SubLevel, - DataValue::SignedNumber(sub_level as i128), - ) - .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); - - let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); - response = response.add_typed_default(DataType::Display, DataValue::Str(display_name)); - - if let Some(s) = status.filter(|s| !s.is_empty()) { - response = response.add_typed_default(DataType::Status, DataValue::Str(s)); - } - if let Some(a) = about.filter(|a| !a.is_empty()) { - response = response.add_typed_default(DataType::About, DataValue::Str(a)); - } - if let Some(av) = avatar { - response = - response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); - } - - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - if let Some(us) = user_status { - let display_status = if us.connection_type == UserStatus::user_invisible { - UserStatus::user_offline - } else { - us.connection_type.clone() - }; - response = response.add_typed_default( - DataType::OnlineStatus, - DataValue::Str(display_status.to_string()), - ); - response = response.add_typed_default( - DataType::OmikronId, - DataValue::SignedNumber(us.omikron_id.into()), - ); - } else { - response = response.add_typed_default( - DataType::OnlineStatus, - DataValue::Str(UserStatus::iota_offline.to_string()), - ); - } - - response = response.add_typed_default( - DataType::OmikronConnections, - DataValue::Array( - iota_connections - .into_iter() - .map(|id| DataValue::SignedNumber(id as i128)) - .collect(), - ), - ); - - response - } - - async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; - } - } - - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(user_id as i64).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id as i64), - None, - ) - .await; - return self.send(&response).await; - } - } - } - - if let Some(username) = cv.get_data(DataType::Username).as_str() { - if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_username(username).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id), - Some(username.to_string()), - ) - .await; - return self.send(&response).await; - } - } - } - - let response = - CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_iota_data_response( - self: Arc, - msg_id: u32, - iota_id: i64, - public_key: PublicKeyBundle, - user_id: Option, - username: Option, - ) -> CommunicationValue { - let mut response = CommunicationValue::new(CommunicationType::GetIotaData) - .with_id(msg_id) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); - - if let Some(uid) = user_id { - response = - response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); - } - if let Some(uname) = username { - response = response.add_typed_default(DataType::Username, DataValue::Str(uname)); - } - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - response.add_typed_default( - DataType::OmikronConnections, - DataValue::Array( - iota_connections - .into_iter() - .map(|id| DataValue::SignedNumber(id as i128)) - .collect(), - ), - ) - } - - async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::GetRegister) - .with_id(cv.get_id()) - .add_typed_default( - DataType::UserId, - DataValue::SignedNumber(register_id as i128), - ); - self.send(&response).await - } - - async fn handle_complete_register_iota( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let iota_id_opt = cv.get_data(DataType::IotaId).as_number().map(|n| n as i64); - - let public_key = cv - .get_data(DataType::PublicKey) - .as_str() - .and_then(|s| PublicKeyBundle::from_base64(s).ok()); - - if let Some(public_key) = public_key { - if let Some(iota_id) = iota_id_opt { - match sql::register_complete_iota(iota_id, public_key).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::Success) - .with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - match sql::create_new_iota(public_key).await { - Ok(new_iota_id) => { - let response = - CommunicationValue::new(CommunicationType::CompleteRegisterIota) - .with_id(cv.get_id()) - .add_typed_default( - DataType::IotaId, - DataValue::SignedNumber(new_iota_id.into()), - ); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) - .await - } - } - - async fn handle_complete_register_user( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_data(DataType::UserId).as_number().map(|n| n as i64); - let username = cv - .get_data(DataType::Username) - .as_str() - .map(|s| s.to_string()); - let public_key = cv - .get_data(DataType::PublicKey) - .as_str() - .and_then(|s| PublicKeyBundle::from_base64(s).ok()); - let iota_id = cv.get_sender(); - let reset_token = cv - .get_data(DataType::ResetToken) - .as_str() - .map(|s| s.to_string()); - - if let (Some(uid), Some(uname), Some(pk), Some(rt)) = - (user_id, username, public_key, reset_token) - { - match sql::register_complete_user(uid, uname, pk, iota_id as i64, rt).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) - .await - } - } - - async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - let mut success = true; - let mut error_message = String::new(); - - if let Some(username) = cv.get_data(DataType::Username).as_str() { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(display) = cv.get_data(DataType::Display).as_str() { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(avatar) = cv.get_data(DataType::Avatar).as_str() { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(about) = cv.get_data(DataType::About).as_str() { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(status) = cv.get_data(DataType::Status).as_str() { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataType::PublicKey) - .as_str() - .and_then(|s| PublicKeyBundle::from_base64(s).ok()), - cv.get_data(DataType::PrivateKeyHash).as_str(), - ) { - if let Err(e) = - sql::change_keys(user_id, public_key, private_key_hash.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } else { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); - self.send(&response).await - } - } - - async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - - if let (iota_id, Some(reset_token), Some(new_token)) = ( - cv.get_sender(), - cv.get_data(DataType::ResetToken).as_str(), - cv.get_data(DataType::NewToken).as_str(), - ) { - match sql::get_by_user_id(user_id).await { - Ok(user) => { - let current_token = user.11; - if current_token == reset_token { - let mut success = true; - let mut error_message = String::new(); - - if let Err(e) = sql::change_iota_id(user_id, iota_id as i64).await { - success = false; - error_message = e.to_string(); - } - if success { - if let Err(e) = sql::change_token(user_id, new_token.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::Success) - .with_id(cv.get_id()); - self.send(&response).await - } else { - let response = - CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error_message), - ); - self.send(&response).await - } - } else { - self.send_error_response( - cv.get_id(), - CommunicationType::ErrorInvalidChallenge, - ) - .await - } - } - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound) - .await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) - .await - } - } - - async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - match sql::delete_user(user_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - - async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let iota_id = cv.get_sender(); - match sql::delete_iota(iota_id as i64).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - - async fn handle_get_notifications( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - let response_array = match sql::get_notifications(user_id).await { - Ok(notifications) => notifications - .into_iter() - .map(|(sender, amount)| { - let tm = mtp::type_map::TypeMap::latest(); - DataValue::Container(vec![ - ( - DataType::SenderId.to_id(&tm), - DataValue::SignedNumber(sender.into()), - ), - ( - DataType::Amount.to_id(&tm), - DataValue::SignedNumber(amount.into()), - ), - ]) - }) - .collect(), - Err(e) => { - log!(PrintType::General, "SQL get_notifications error: {}", e); - vec![] - } - }; - - let response = CommunicationValue::new(CommunicationType::GetNotifications) - .with_id(cv.get_id()) - .add_typed_default(DataType::Notifications, DataValue::Array(response_array)); - self.send(&response).await - } - - async fn handle_read_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let receiver_id = match cv.get_sender() { - s if s > 0 => s as i64, - _ => match cv.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; - - if let Some(other_id) = cv - .get_data(DataType::SenderId) - .as_number() - .map(|n| n as i64) - { - if let Err(e) = sql::read_notification(receiver_id, other_id).await { - log!(PrintType::General, "SQL read_notification error: {}", e); - } else { - let response = CommunicationValue::new(CommunicationType::ReadNotification) - .with_id(cv.get_id()); - let _ = self.send(&response).await; - - let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) - .with_receiver(receiver_id as u64) - .add_typed_default( - DataType::SenderId, - DataValue::SignedNumber(other_id.into()), - ); - crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; - } - } - Ok(()) - } - - async fn handle_push_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let receiver_id = match cv.get_receiver() { - r if r > 0 => r as i64, - _ => match cv.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; - - let sender_id = match cv.get_data(DataType::SenderId).as_number() { - Some(id) => id as i64, - None => cv.get_sender() as i64, - }; - - if let Err(e) = sql::add_notification(receiver_id, sender_id).await { - log!(PrintType::General, "SQL add_notification error: {}", e); - } else { - let response = - CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); - let _ = self.send(&response).await; - - let push_cv = CommunicationValue::new(CommunicationType::PushNotification) - .with_receiver(receiver_id as u64) - .add_typed_default( - DataType::SenderId, - DataValue::SignedNumber(sender_id.into()), - ); - crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; - } - Ok(()) - } - - async fn handle_get_states(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_ids = match cv.get_data(DataType::UserIds) { - DataValue::Array(ids) => ids, - _ => return Ok(()), - }; - - let mut states = Vec::new(); - for id_val in user_ids { - if let DataValue::SignedNumber(user_id) = id_val { - let user_id = *user_id as i64; - let status = user_online_tracker::get_user_status(user_id); - let status_str = match status { - Some(ref us) => { - if us.connection_type == UserStatus::user_invisible { - "user_offline".to_string() - } else { - us.connection_type.to_string() - } - } - None => UserStatus::iota_offline.to_string(), - }; - let tm = mtp::type_map::TypeMap::latest(); - let mut map = Vec::new(); - map.push(( - DataType::UserId.to_id(&tm), - DataValue::SignedNumber(user_id.into()), - )); - map.push((DataType::UserState.to_id(&tm), DataValue::Str(status_str))); - states.push(DataValue::Container(map)); - } - } - - let response = CommunicationValue::new(CommunicationType::GetStates) - .with_id(cv.get_id()) - .add_typed_default(DataType::UserStates, DataValue::Array(states)); - self.send(&response).await - } - - async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { + async fn ping(self: Arc, value: CommunicationValue) -> OmikronResult<()> { + if let DataValue::SignedNumber(last_ping) = value.get_data(DataType::LastPing) { *self.ping.write().await = *last_ping as i64; } - - let response = CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); + let response = CommunicationValue::new(CommunicationType::Pong).with_id(value.get_id()); self.send(&response).await } - - async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { - log_cv_out!(PrintType::Omikron, cv); + pub(crate) async fn send(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { + if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { + log_cv_out!(PrintType::Omikron, value); } - let guard = self.sender.lock().await; - let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; - + let sender = guard + .as_ref() + .ok_or(crate::error::OmegaError::NotConnected)?; sender - .send(cv) + .send(value) .await - .map_err(|e| OmikronError::Send(e.to_string())) + .map_err(|error| crate::error::OmegaError::SendError(error.to_string())) } - - async fn send_error_response( + pub(crate) async fn send_error_response( self: Arc, message_id: u32, error_type: CommunicationType, ) -> OmikronResult<()> { - let error = CommunicationValue::new(error_type).with_id(message_id); - self.send(&error).await + self.send(&CommunicationValue::new(error_type).with_id(message_id)) + .await } - pub async fn close(self: Arc) { log_in!( self.get_omikron_id().await.unwrap_or(0), @@ -962,51 +228,38 @@ impl OmikronConnection { "Omikron connection Closed" ); } - async fn cleanup(self: Arc) { if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); omikron_manager::remove_omikron(self.id as i64).await; - user_online_tracker::untrack_omikron(self.id as i64).await; + crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; } - if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); } } - pub async fn get_omikron_id(self: Arc) -> Option { Some(self.id as i64) } - - pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - self.send(cv).await + pub async fn send_message(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { + self.send(value).await } } -// ============================================================================ -// Server Startup -// ============================================================================ - -pub async fn get_by_omikron_id( - omikron_id: u64, - _description: Option, -) -> Option { - sql::get_omikron_by_id(omikron_id as i64) +pub async fn get_by_omikron_id(omikron_id: u64, _: Option) -> Option { + crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64)) .await .ok() - .map(|(bundle, _ip_address, _port)| bundle) + .map(|omikron| omikron.public_key) } -pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option) -> u64 { +pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { 0 } pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - + let cert_pem = load_file_vec("certs", "transport_cert.pem")?; + let key_pem = load_file_vec("certs", "transport_key.pem")?; let web_config = server::server::build_web_config()?; - let host_config = HostConfig::new( IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, @@ -1034,30 +287,51 @@ pub async fn start(port: u16) -> Result<(), Box> { }) .with_authentication( load_keyring(), - Box::new(|user_id, description| Box::pin(get_by_omikron_id(user_id, description))), - Box::new(|pub_key, description| Box::pin(complete_register(pub_key, description))), + Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))), + Box::new(|key, description| Box::pin(complete_register(key, description))), ) .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); - let mut server = MTPWebServer::new(host_config, web_config).await?; log!("OmegaServer listening on port {}", port); - loop { let mut conn = match server.accept().await { Ok(Some(conn)) => conn, Ok(None) => break, - Err(e) => { - log_err!(0, PrintType::Omega, "Rejected omikron connection: {}", e); + Err(error) => { + log_err!( + 0, + PrintType::Omega, + "Rejected omikron connection: {}", + error + ); continue; } }; - - let omikron_connection = OmikronConnection::new(conn.sender, conn.client_id); + let config = crate::config::RateLimitConfig::from_env(); + let peer_ip = conn.remote_addr.map(|address| address.ip()); + let active = ACTIVE_CONNECTIONS.fetch_add(1, Ordering::AcqRel) + 1; + let peer_active = peer_ip.map(|ip| { + let mut count = ACTIVE_CONNECTIONS_BY_IP.entry(ip).or_insert(0); + *count += 1; + *count + }); + if active > config.transport_connections + || peer_active.is_some_and(|count| count > config.transport_connections_per_ip) + { + drop(ConnectionLimitGuard(peer_ip)); + log_err!( + 0, + PrintType::Omega, + "Rejected connection: active connection limit reached" + ); + continue; + } + let connection = OmikronConnection::new(conn.sender, conn.client_id); tokio::spawn(async move { - omikron_manager::add_omikron(omikron_connection.clone()).await; - omikron_connection.handle(&mut conn.receiver).await; + let _guard = ConnectionLimitGuard(peer_ip); + omikron_manager::add_omikron(connection.clone()).await; + connection.handle(&mut conn.receiver).await; }); } - Ok(()) } diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index f98a75c..2de4ea3 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,4 +1,4 @@ -use crate::transport::omikron_connection::OmikronConnection; +use crate::transport::connection::OmikronConnection; use dashmap::DashMap; use mtp::codec::CommunicationValue; use once_cell::sync::Lazy; @@ -27,11 +27,9 @@ pub async fn remove_omikron(omikron_id: i64) { } pub async fn get_random_omikron() -> Result, ()> { - let mut rng = rand::thread_rng(); - let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); - if let Some(key) = keys.into_iter().choose(&mut rng) { + if let Some(key) = keys.into_iter().choose(&mut rand::rng()) { if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { return Ok(entry.clone()); } diff --git a/src/util/file_util.rs b/src/util/file_util.rs index 593adbe..8630489 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -8,9 +8,8 @@ use zip::ZipArchive; use crate::log; -static WORKING_DIR: Lazy = Lazy::new(|| { - std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) -}); +static WORKING_DIR: Lazy = + Lazy::new(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); pub fn delete_file(path: &str, name: &str) -> bool { let dir = Path::new(&get_directory()).join(path); diff --git a/src/util/logger.rs b/src/util/logger.rs index 31606b6..39eb933 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -269,8 +269,7 @@ fn format_data_container(data: Vec<(DataTypeId, DataValue)>, version: Version) - DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), DataValue::Container(inner) => { - let inner_formatted = - format_data_container(inner, version.clone()); + let inner_formatted = format_data_container(inner, version.clone()); format!("{}={{ {} }}", key_str, inner_formatted) } From ed1b21b3ff473317b52191240b40e36836af2e10 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 20 Jul 2026 22:21:43 +0200 Subject: [PATCH 184/220] [Add] Structure --- Cargo.lock | 720 +++------------- Cargo.toml | 32 +- migrations/001.sql | 44 + src/api/mod.rs | 1 + src/api/response.rs | 70 ++ src/config.rs | 65 ++ src/db/iota_repo.rs | 53 ++ src/db/mod.rs | 38 + src/db/notification_repo.rs | 38 + src/db/omikron_repo.rs | 29 + src/db/short_link_repo.rs | 35 + src/db/user_repo.rs | 194 +++++ src/error.rs | 57 ++ src/main.rs | 53 +- src/models/ids.rs | 30 + src/models/iota.rs | 9 + src/models/mod.rs | 21 + src/models/notification.rs | 9 + src/models/omikron.rs | 12 + src/models/user.rs | 19 + src/server/api.rs | 546 +++++------- src/server/middleware.rs | 84 ++ src/server/mod.rs | 2 + src/server/short_link.rs | 46 +- src/server/validation.rs | 42 + src/sql/mod.rs | 1 - src/sql/sql.rs | 778 ----------------- src/sql/user_online_tracker.rs | 105 ++- src/transport/connection.rs | 1 + src/transport/handlers/account.rs | 43 + src/transport/handlers/calls.rs | 1 + src/transport/handlers/links.rs | 21 + src/transport/handlers/messaging.rs | 1 + src/transport/handlers/mod.rs | 9 + src/transport/handlers/notifications.rs | 116 +++ src/transport/handlers/presence.rs | 128 +++ src/transport/handlers/register.rs | 151 ++++ src/transport/handlers/states.rs | 46 + src/transport/handlers/user_data.rs | 271 ++++++ src/transport/mod.rs | 2 + src/transport/omikron_connection.rs | 1020 ++++------------------- src/transport/omikron_manager.rs | 6 +- src/util/file_util.rs | 5 +- src/util/logger.rs | 3 +- 44 files changed, 2223 insertions(+), 2734 deletions(-) create mode 100644 migrations/001.sql create mode 100644 src/api/mod.rs create mode 100644 src/api/response.rs create mode 100644 src/config.rs create mode 100644 src/db/iota_repo.rs create mode 100644 src/db/mod.rs create mode 100644 src/db/notification_repo.rs create mode 100644 src/db/omikron_repo.rs create mode 100644 src/db/short_link_repo.rs create mode 100644 src/db/user_repo.rs create mode 100644 src/error.rs create mode 100644 src/models/ids.rs create mode 100644 src/models/iota.rs create mode 100644 src/models/mod.rs create mode 100644 src/models/notification.rs create mode 100644 src/models/omikron.rs create mode 100644 src/models/user.rs create mode 100644 src/server/middleware.rs create mode 100644 src/server/validation.rs delete mode 100644 src/sql/sql.rs create mode 100644 src/transport/connection.rs create mode 100644 src/transport/handlers/account.rs create mode 100644 src/transport/handlers/calls.rs create mode 100644 src/transport/handlers/links.rs create mode 100644 src/transport/handlers/messaging.rs create mode 100644 src/transport/handlers/mod.rs create mode 100644 src/transport/handlers/notifications.rs create mode 100644 src/transport/handlers/presence.rs create mode 100644 src/transport/handlers/register.rs create mode 100644 src/transport/handlers/states.rs create mode 100644 src/transport/handlers/user_data.rs diff --git a/Cargo.lock b/Cargo.lock index 4ce5e8d..a20fbae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,27 +18,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "aead" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" -dependencies = [ - "crypto-common 0.2.2", - "inout 0.2.2", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher 0.4.4", - "cpufeatures 0.2.17", -] - [[package]] name = "aes" version = "0.9.1" @@ -50,20 +29,6 @@ dependencies = [ "cpufeatures 0.3.0", ] -[[package]] -name = "aes-gcm" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" -dependencies = [ - "aead 0.6.1", - "aes 0.9.1", - "cipher 0.5.2", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -79,21 +44,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "asn1-rs" version = "0.7.2" @@ -133,148 +83,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-executor" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" -dependencies = [ - "async-task", - "concurrent-queue", - "fastrand 2.5.0", - "futures-lite 2.6.1", - "pin-project-lite", - "slab", -] - -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io 2.6.0", - "async-lock 3.4.2", - "blocking", - "futures-lite 2.6.1", - "once_cell", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock 2.8.0", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite 1.13.0", - "log", - "parking", - "polling 2.8.0", - "rustix 0.37.28", - "slab", - "socket2 0.4.10", - "waker-fn", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite 2.6.1", - "parking", - "polling 3.11.0", - "rustix 1.1.4", - "slab", - "windows-sys 0.61.2", -] - -[[package]] -name = "async-lock" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" -dependencies = [ - "event-listener 2.5.3", -] - -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener 5.4.1", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-channel 1.9.0", - "async-global-executor", - "async-io 2.6.0", - "async-lock 3.4.2", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite 2.6.1", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-task" -version = "4.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" - [[package]] name = "async-trait" version = "0.1.91" @@ -283,7 +91,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 3.0.2", ] [[package]] @@ -352,12 +160,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.13.1" @@ -383,19 +185,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", -] - -[[package]] -name = "blocking" -version = "1.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" -dependencies = [ - "async-channel 2.5.0", - "async-task", - "futures-io", - "futures-lite 2.6.1", - "piper", + "zeroize", ] [[package]] @@ -477,7 +267,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ - "aead 0.5.2", + "aead", "chacha20 0.9.1", "cipher 0.4.4", "poly1305", @@ -501,7 +291,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "block-buffer 0.12.1", "crypto-common 0.2.2", "inout 0.2.2", ] @@ -554,9 +343,9 @@ checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "core-foundation" @@ -669,15 +458,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "ctr" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" -dependencies = [ - "cipher 0.5.2", -] - [[package]] name = "ctutils" version = "0.4.2" @@ -697,7 +477,7 @@ dependencies = [ "cpufeatures 0.2.17", "curve25519-dalek-derive", "digest 0.10.7", - "fiat-crypto 0.2.9", + "fiat-crypto", "rustc_version", "subtle", "zeroize", @@ -781,17 +561,6 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -[[package]] -name = "derive_arbitrary" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "digest" version = "0.10.7" @@ -814,6 +583,7 @@ dependencies = [ "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", + "zeroize", ] [[package]] @@ -869,17 +639,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed448-goldilocks" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" -dependencies = [ - "fiat-crypto 0.1.20", - "hex", - "subtle", -] - [[package]] name = "either" version = "1.16.0" @@ -925,12 +684,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "event-listener" version = "5.4.1" @@ -942,16 +695,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener 5.4.1", - "pin-project-lite", -] - [[package]] name = "fastbloom" version = "0.17.0" @@ -964,27 +707,12 @@ dependencies = [ "siphasher", ] -[[package]] -name = "fastrand" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] - [[package]] name = "fastrand" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" -[[package]] -name = "fiat-crypto" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" - [[package]] name = "fiat-crypto" version = "0.2.9" @@ -1110,34 +838,6 @@ version = "0.3.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand 1.9.0", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "fastrand 2.5.0", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.33" @@ -1201,18 +901,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -1222,32 +910,11 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi 6.0.0", + "r-efi", "rand_core 0.10.1", "wasm-bindgen", ] -[[package]] -name = "ghash" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" -dependencies = [ - "polyval", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.4.15" @@ -1274,7 +941,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10872b55cfb02a821b69dc7cf8dc6a71d6af25eb9a79662bec4a9d016056b3be" dependencies = [ "bytes", - "fastrand 2.5.0", + "fastrand", "futures-util", "http", "pin-project-lite", @@ -1361,18 +1028,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -1481,9 +1136,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" dependencies = [ "atomic-waker", "bytes", @@ -1532,7 +1187,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.5", + "socket2", "system-configuration", "tokio", "tower-service", @@ -1671,26 +1326,6 @@ dependencies = [ "hybrid-array", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -1773,12 +1408,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - [[package]] name = "keccak" version = "0.1.6" @@ -1798,15 +1427,6 @@ dependencies = [ "cpufeatures 0.3.0", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -1840,7 +1460,7 @@ version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" dependencies = [ - "bitflags 2.13.1", + "bitflags", "libc", "plain", "redox_syscall 0.9.0", @@ -1856,18 +1476,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "linux-raw-sys" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - [[package]] name = "litemap" version = "0.8.2" @@ -1888,9 +1496,6 @@ name = "log" version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" -dependencies = [ - "value-bag", -] [[package]] name = "lru-slab" @@ -1900,12 +1505,11 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "lzma-rust2" -version = "0.13.0" +version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c60a23ffb90d527e23192f1246b14746e2f7f071cb84476dd879071696c18a4a" +checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" dependencies = [ - "crc", - "sha2 0.10.9", + "sha2 0.11.0", ] [[package]] @@ -2252,31 +1856,25 @@ dependencies = [ name = "omega" version = "0.1.0" dependencies = [ - "aes-gcm", "ansi_term", - "anyhow", "base64", "bytes", "dashmap", "dotenv", - "hex", - "hkdf 0.12.4", "http", - "json", "mtp", "once_cell", - "rand 0.8.7", - "rand_core 0.6.4", + "rand 0.10.2", "reqwest", "rustls", - "sha2 0.10.9", + "serde", + "serde_json", "sqlx", "strum", "strum_macros", "thiserror 2.0.19", "tokio", "uuid", - "x448", "zip", ] @@ -2329,12 +1927,12 @@ dependencies = [ [[package]] name = "pbkdf2" -version = "0.12.2" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" dependencies = [ - "digest 0.10.7", - "hmac 0.12.1", + "digest 0.11.3", + "hmac 0.13.0", ] [[package]] @@ -2368,23 +1966,6 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "piper" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" -dependencies = [ - "atomic-waker", - "fastrand 2.5.0", - "futures-io", -] - [[package]] name = "pkcs1" version = "0.7.5" @@ -2428,36 +2009,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys 0.48.0", -] - -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi 0.5.2", - "pin-project-lite", - "rustix 1.1.4", - "windows-sys 0.61.2", -] - [[package]] name = "poly1305" version = "0.8.0" @@ -2466,18 +2017,7 @@ checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures 0.2.17", "opaque-debug", - "universal-hash 0.5.1", -] - -[[package]] -name = "polyval" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" -dependencies = [ - "cpubits", - "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", ] [[package]] @@ -2539,7 +2079,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.5", + "socket2", "thiserror 2.0.19", "tokio", "tracing", @@ -2580,7 +2120,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.5", + "socket2", "tracing", "windows-sys 0.61.2", ] @@ -2594,12 +2134,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -2638,12 +2172,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" - [[package]] name = "rand_core" version = "0.6.4" @@ -2689,7 +2217,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.13.1", + "bitflags", ] [[package]] @@ -2698,7 +2226,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" dependencies = [ - "bitflags 2.13.1", + "bitflags", ] [[package]] @@ -2797,33 +2325,6 @@ dependencies = [ "nom", ] -[[package]] -name = "rustix" -version = "0.37.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" -dependencies = [ - "bitflags 1.3.2", - "errno", - "io-lifetimes", - "libc", - "linux-raw-sys 0.3.8", - "windows-sys 0.48.0", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", -] - [[package]] name = "rustls" version = "0.23.42" @@ -2943,7 +2444,7 @@ version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "bitflags 2.13.1", + "bitflags", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -2993,14 +2494,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 3.0.2", ] [[package]] name = "serde_json" -version = "1.0.150" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -3045,6 +2546,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3167,16 +2679,6 @@ dependencies = [ "serde", ] -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "socket2" version = "0.6.5" @@ -3241,14 +2743,12 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "async-io 1.13.0", - "async-std", "base64", "bytes", "crc", "crossbeam-queue", "either", - "event-listener 5.4.1", + "event-listener", "futures-core", "futures-intrusive", "futures-io", @@ -3265,6 +2765,8 @@ dependencies = [ "sha2 0.10.9", "smallvec", "thiserror 2.0.19", + "tokio", + "tokio-stream", "tracing", "url", ] @@ -3288,7 +2790,6 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ - "async-std", "dotenvy", "either", "heck", @@ -3304,6 +2805,7 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "tokio", "url", ] @@ -3315,7 +2817,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64", - "bitflags 2.13.1", + "bitflags", "byteorder", "bytes", "crc", @@ -3339,7 +2841,7 @@ dependencies = [ "rand 0.8.7", "rsa", "serde", - "sha1", + "sha1 0.10.7", "sha2 0.10.9", "smallvec", "sqlx-core", @@ -3357,7 +2859,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64", - "bitflags 2.13.1", + "bitflags", "byteorder", "crc", "dotenvy", @@ -3429,15 +2931,15 @@ dependencies = [ [[package]] name = "strum" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" [[package]] name = "strum_macros" -version = "0.27.2" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" dependencies = [ "heck", "proc-macro2", @@ -3464,9 +2966,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" dependencies = [ "proc-macro2", "quote", @@ -3499,7 +3001,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ - "bitflags 2.13.1", + "bitflags", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3551,16 +3053,17 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.1", + "syn 3.0.2", ] [[package]] name = "time" -version = "0.3.53" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" dependencies = [ "deranged", + "js-sys", "num-conv", "powerfmt", "serde_core", @@ -3576,9 +3079,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.31" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", @@ -3611,9 +3114,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.53.0" +version = "1.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" dependencies = [ "bytes", "libc", @@ -3621,7 +3124,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.5", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -3647,6 +3150,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -3681,7 +3195,7 @@ version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ - "bitflags 2.13.1", + "bitflags", "bytes", "futures-util", "http", @@ -3743,6 +3257,12 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typenum" version = "1.20.1" @@ -3786,16 +3306,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "universal-hash" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" -dependencies = [ - "crypto-common 0.2.2", - "ctutils", -] - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -3843,12 +3353,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "value-bag" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd4ec1eb1d240636e354a30110a1dfcb37047169a4d9bd6d9d3469df574b5c4" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3861,12 +3365,6 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" -[[package]] -name = "waker-fn" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7" - [[package]] name = "walkdir" version = "2.5.0" @@ -3892,15 +3390,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasite" version = "0.1.0" @@ -4215,12 +3704,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "writeable" version = "0.6.3" @@ -4241,7 +3724,7 @@ dependencies = [ "rustls-native-certs", "rustls-pki-types", "sha2 0.11.0", - "socket2 0.6.5", + "socket2", "thiserror 2.0.19", "time", "tokio", @@ -4275,17 +3758,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "x448" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" -dependencies = [ - "ed448-goldilocks", - "hex", - "rand_core 0.5.1", -] - [[package]] name = "x509-parser" version = "0.18.1" @@ -4340,18 +3812,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.54" +version = "0.8.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" dependencies = [ "proc-macro2", "quote", @@ -4434,26 +3906,26 @@ dependencies = [ [[package]] name = "zip" -version = "6.0.0" +version = "8.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2a05c7c36fde6c09b08576c9f7fb4cda705990f73b58fe011abf7dfb24168b" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" dependencies = [ - "aes 0.8.4", - "arbitrary", + "aes", "bzip2", "constant_time_eq", "crc32fast", "deflate64", "flate2", - "getrandom 0.3.4", - "hmac 0.12.1", + "getrandom 0.4.3", + "hmac 0.13.0", "indexmap", "lzma-rust2", "memchr", "pbkdf2", "ppmd-rust", - "sha1", + "sha1 0.11.0", "time", + "typed-path", "zeroize", "zopfli", "zstd", diff --git a/Cargo.toml b/Cargo.toml index e601d1a..3b4f6a3 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,33 +10,27 @@ mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ "web-server", ] } -aes-gcm = "*" ansi_term = "0.12.1" -anyhow = "1.0.101" base64 = "0.22.1" bytes = "1" -dashmap = "6.1.0" +dashmap = "6.2.1" dotenv = "0.15.0" -hex = "0.4.3" -hkdf = "0.12.4" http = "1" -json = "0.12.4" -once_cell = "1.21.3" -rand = "0.8" -rand_core = { version = "0.6", features = ["getrandom", "std"] } -reqwest = { version = "0.13.2" } -rustls = { version = "0.23.37", default-features = false, features = [ +once_cell = "1.21.4" +rand = "0.10.2" +reqwest = "0.13.4" +rustls = { version = "0.23.42", default-features = false, features = [ "std", "tls12", "aws-lc-rs", "prefer-post-quantum", ] } -sha2 = "0.10.9" -sqlx = { version = "0.8.6", features = ["mysql", "runtime-async-std"] } -strum = "0.27.2" -strum_macros = "0.27.2" +sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } +strum = "0.28.0" +strum_macros = "0.28.0" tokio = { version = "*", features = ["full"] } -uuid = { version = "1.19.0", features = ["v4"] } -x448 = "0.6.0" -zip = "6.0.0" -thiserror = "2.0.18" +uuid = { version = "1.24.0", features = ["v4", "v7"] } +zip = "8.6.0" +thiserror = "2.0.19" +serde = { version = "1.0.229", features = ["derive"] } +serde_json = "1.0.151" diff --git a/migrations/001.sql b/migrations/001.sql new file mode 100644 index 0000000..dc3a096 --- /dev/null +++ b/migrations/001.sql @@ -0,0 +1,44 @@ +CREATE TABLE IF NOT EXISTS users ( + id BIGINT NOT NULL PRIMARY KEY, + iota_id BIGINT NOT NULL, + username VARBINARY(255) NOT NULL, + display VARBINARY(255), + status VARBINARY(255), + about VARBINARY(1000), + avatar BLOB, + sub_level INT NOT NULL DEFAULT 0, + sub_end BIGINT NOT NULL DEFAULT 0, + public_key BLOB NOT NULL, + token BLOB NOT NULL, + UNIQUE KEY uk_users_username (username), + UNIQUE KEY uk_users_iota_id (iota_id) +); + +CREATE TABLE IF NOT EXISTS iotas ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL +); + +CREATE TABLE IF NOT EXISTS omikrons ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL, + location VARBINARY(255) NOT NULL, + ip_address VARBINARY(45) NOT NULL, + port INT NOT NULL +); + +CREATE TABLE IF NOT EXISTS notifications ( + id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY, + sender_id BIGINT NOT NULL, + receiver_id BIGINT NOT NULL, + amount BIGINT NOT NULL DEFAULT 1, + UNIQUE KEY uk_notifications_sender_receiver (sender_id, receiver_id), + INDEX idx_notifications_receiver (receiver_id) +); + +CREATE TABLE IF NOT EXISTS short_links ( + short_key VARCHAR(12) CHARACTER SET ascii COLLATE ascii_bin NOT NULL PRIMARY KEY, + long_url TEXT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + INDEX idx_short_links_created_at (created_at) +); diff --git a/src/api/mod.rs b/src/api/mod.rs new file mode 100644 index 0000000..4c6f2cd --- /dev/null +++ b/src/api/mod.rs @@ -0,0 +1 @@ +pub mod response; diff --git a/src/api/response.rs b/src/api/response.rs new file mode 100644 index 0000000..e8489b5 --- /dev/null +++ b/src/api/response.rs @@ -0,0 +1,70 @@ +use serde::Serialize; +use std::collections::BTreeMap; + +#[derive(Serialize)] +pub struct StatusResponse { + pub status: &'static str, +} + +#[derive(Serialize)] +pub struct OmikronResponse { + pub status: &'static str, + pub id: i64, + pub public_key: String, + pub ip_address: String, + pub port: u16, +} + +#[derive(Serialize)] +pub struct IotaResponse { + pub status: &'static str, + pub iota_id: i64, + pub public_key: String, +} + +#[derive(Serialize)] +pub struct UserResponse { + pub status: &'static str, + pub username: String, + pub public_key: String, + pub user_id: i64, + pub iota_id: i64, + pub sub_level: i32, + pub sub_end: i64, + #[serde(skip_serializing_if = "Option::is_none")] + pub display: Option, + #[serde(rename = "status_message", skip_serializing_if = "Option::is_none")] + pub status_message: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub about: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub avatar: Option, +} + +#[derive(Serialize)] +pub struct UsernameResponse { + pub status: &'static str, + pub username: String, + pub public_key: String, + pub user_id: i64, + pub iota_id: i64, + pub sub_level: i32, + pub sub_end: i64, +} + +#[derive(Serialize)] +pub struct ConnectionsResponse { + pub status: &'static str, + #[serde(flatten)] + pub connections: BTreeMap>>, +} + +#[derive(Serialize)] +pub struct PublicKeyResponse { + pub status: &'static str, + pub public_key: String, +} + +pub fn json(value: &T) -> String { + serde_json::to_string(value).unwrap_or_else(|_| "{\"status\":\"error\"}".to_string()) +} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..5ba9dc2 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,65 @@ +use std::{env, time::Duration}; + +#[derive(Clone, Debug)] +pub struct RateLimitConfig { + pub window: Duration, + pub general_requests: usize, + pub registration_requests: usize, + pub transport_connections: usize, + pub transport_connections_per_ip: usize, +} + +pub fn cors_origin() -> String { + env::var("CORS_ORIGIN").unwrap_or_else(|_| "https://tensamin.net".to_string()) +} + +impl Default for RateLimitConfig { + fn default() -> Self { + Self { + window: Duration::from_secs(60), + general_requests: 120, + registration_requests: 20, + transport_connections: 512, + transport_connections_per_ip: 32, + } + } +} + +impl RateLimitConfig { + pub fn from_env() -> Self { + let defaults = Self::default(); + Self { + window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window), + general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests), + registration_requests: env_usize( + "RATE_LIMIT_REGISTRATION_REQUESTS", + defaults.registration_requests, + ), + transport_connections: env_usize( + "RATE_LIMIT_TRANSPORT_CONNECTIONS", + defaults.transport_connections, + ), + transport_connections_per_ip: env_usize( + "RATE_LIMIT_TRANSPORT_CONNECTIONS_PER_IP", + defaults.transport_connections_per_ip, + ), + } + } +} + +fn env_usize(name: &str, fallback: usize) -> usize { + env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .unwrap_or(fallback) +} + +fn env_duration(name: &str, fallback: Duration) -> Duration { + env::var(name) + .ok() + .and_then(|value| value.parse::().ok()) + .filter(|value| *value > 0) + .map(Duration::from_secs) + .unwrap_or(fallback) +} diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs new file mode 100644 index 0000000..a5952f1 --- /dev/null +++ b/src/db/iota_repo.rs @@ -0,0 +1,53 @@ +use crate::{ + db::pool, + error::{OmegaError, Result}, + models::{Iota, IotaId}, +}; +use mtp::crypto::PublicKeyBundle; +use sqlx::Row; + +pub async fn get_iota_by_id(id: IotaId) -> Result { + let row = sqlx::query("SELECT id, public_key FROM iotas WHERE id = ?") + .bind(id.0) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + let key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|error| OmegaError::Validation(error.to_string()))?; + Ok(Iota { + id: row.get::("id").into(), + public_key: key, + }) +} + +pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { + let id = crate::db::user_repo::get_register_id().await?; + let iota_id = IotaId::from(id.0); + register_complete_iota(iota_id, public_key).await?; + Ok(iota_id) +} + +pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> Result<()> { + sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") + .bind(id.0) + .bind(public_key.as_bytes()) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> { + sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") + .bind(key.as_bytes()) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} +pub async fn delete_iota(id: IotaId) -> Result<()> { + sqlx::query("DELETE FROM iotas WHERE id = ?") + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..f8afccb --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,38 @@ +use crate::error::{OmegaError, Result}; +use once_cell::sync::Lazy; +use sqlx::{MySql, Pool, mysql::MySqlPoolOptions}; +use std::{env, sync::Arc}; +use tokio::sync::RwLock; + +pub mod iota_repo; +pub mod notification_repo; +pub mod omikron_repo; +pub mod short_link_repo; +pub mod user_repo; + +pub type DbPool = Pool; + +static POOL: Lazy>>> = Lazy::new(|| Arc::new(RwLock::new(None))); + +pub async fn pool() -> Result { + POOL.read() + .await + .as_ref() + .cloned() + .ok_or(OmegaError::DatabaseNotInitialized) +} + +pub async fn initialize() -> Result<()> { + let url = + env::var("DB_URL").map_err(|_| OmegaError::Validation("DB_URL is not set".to_string()))?; + let pool = MySqlPoolOptions::new() + .max_connections(200) + .connect(&url) + .await?; + sqlx::migrate!() + .run(&pool) + .await + .map_err(|error| OmegaError::Validation(format!("database migration failed: {error}")))?; + *POOL.write().await = Some(pool); + Ok(()) +} diff --git a/src/db/notification_repo.rs b/src/db/notification_repo.rs new file mode 100644 index 0000000..b317472 --- /dev/null +++ b/src/db/notification_repo.rs @@ -0,0 +1,38 @@ +use crate::{ + db::pool, + error::Result, + models::{Notification, UserId}, +}; +use sqlx::Row; + +pub async fn add_notification(sender_id: UserId, receiver_id: UserId) -> Result<()> { + sqlx::query("INSERT INTO notifications (sender_id, receiver_id, amount) VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE amount = amount + 1").bind(sender_id.0).bind(receiver_id.0).execute(&pool().await?).await?; + Ok(()) +} + +pub async fn read_notification(sender_id: UserId, receiver_id: UserId) -> Result<()> { + sqlx::query("DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ?") + .bind(sender_id.0) + .bind(receiver_id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn get_notifications(receiver_id: UserId) -> Result> { + let rows = sqlx::query( + "SELECT id, sender_id, receiver_id, amount FROM notifications WHERE receiver_id = ?", + ) + .bind(receiver_id.0) + .fetch_all(&pool().await?) + .await?; + Ok(rows + .into_iter() + .map(|row| Notification { + id: row.get("id"), + sender_id: row.get::("sender_id").into(), + receiver_id: row.get::("receiver_id").into(), + amount: row.get("amount"), + }) + .collect()) +} diff --git a/src/db/omikron_repo.rs b/src/db/omikron_repo.rs new file mode 100644 index 0000000..03e1661 --- /dev/null +++ b/src/db/omikron_repo.rs @@ -0,0 +1,29 @@ +use crate::{ + db::pool, + error::{OmegaError, Result}, + models::{Omikron, OmikronId}, +}; +use mtp::crypto::PublicKeyBundle; +use sqlx::Row; + +pub async fn get_omikron_by_id(id: OmikronId) -> Result { + let row = + sqlx::query("SELECT id, public_key, location, ip_address, port FROM omikrons WHERE id = ?") + .bind(id.0) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + let key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) + .map_err(|error| OmegaError::Validation(error.to_string()))?; + let location = String::from_utf8(row.get("location")) + .map_err(|error| OmegaError::Validation(format!("invalid location UTF-8: {error}")))?; + let ip_address = String::from_utf8(row.get("ip_address")) + .map_err(|error| OmegaError::Validation(format!("invalid IP address UTF-8: {error}")))?; + Ok(Omikron { + id: row.get::("id").into(), + public_key: key, + location, + ip_address, + port: row.get::("port") as u16, + }) +} diff --git a/src/db/short_link_repo.rs b/src/db/short_link_repo.rs new file mode 100644 index 0000000..24d85ac --- /dev/null +++ b/src/db/short_link_repo.rs @@ -0,0 +1,35 @@ +use crate::{db::pool, error::Result}; + +pub async fn count() -> Result { + let count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM short_links") + .fetch_one(&pool().await?) + .await?; + Ok(count.max(0) as u64) +} + +pub async fn insert(short_key: &str, long_url: &str) -> Result { + let result = sqlx::query("INSERT IGNORE INTO short_links (short_key, long_url) VALUES (?, ?)") + .bind(short_key) + .bind(long_url) + .execute(&pool().await?) + .await?; + Ok(result.rows_affected() == 1) +} + +pub async fn get(short_key: &str) -> Result> { + Ok( + sqlx::query_scalar::<_, String>("SELECT long_url FROM short_links WHERE short_key = ?") + .bind(short_key) + .fetch_optional(&pool().await?) + .await?, + ) +} + +pub async fn delete_expired() -> Result { + let result = sqlx::query( + "DELETE FROM short_links WHERE created_at < CURRENT_TIMESTAMP - INTERVAL 7 DAY", + ) + .execute(&pool().await?) + .await?; + Ok(result.rows_affected()) +} diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs new file mode 100644 index 0000000..ca71dd3 --- /dev/null +++ b/src/db/user_repo.rs @@ -0,0 +1,194 @@ +use crate::{ + db::pool, + error::{OmegaError, Result}, + models::{IotaId, User, UserId}, +}; +use mtp::crypto::PublicKeyBundle; +use sqlx::FromRow; + +pub async fn get_register_id() -> Result { + let bytes = *uuid::Uuid::now_v7().as_bytes(); + let id = + i64::from_be_bytes(bytes[8..].try_into().map_err(|_| { + OmegaError::Validation("generated ID has an invalid length".to_string()) + })?) & i64::MAX; + Ok(UserId::from(id.max(1))) +} + +const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; +const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; +const USERS_BY_IOTA_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE iota_id = ?"; + +#[derive(FromRow)] +struct UserRow { + id: i64, + iota_id: i64, + username: Vec, + display: Option>, + status: Option>, + about: Option>, + avatar: Option>, + sub_level: i32, + sub_end: i64, + public_key: Vec, + token: Vec, +} + +impl TryFrom for User { + type Error = sqlx::Error; + + fn try_from(row: UserRow) -> std::result::Result { + let public_key = PublicKeyBundle::from_bytes(&row.public_key) + .map_err(|error| sqlx::Error::Decode(Box::new(error)))?; + let decode = + |value| String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error))); + Ok(User { + id: row.id.into(), + iota_id: row.iota_id.into(), + username: decode(row.username)?, + display: row.display.map(decode).transpose()?, + status: row.status.map(decode).transpose()?, + about: row.about.map(decode).transpose()?, + avatar: row.avatar, + sub_level: row.sub_level, + sub_end: row.sub_end, + public_key, + token: decode(row.token)?, + }) + } +} + +pub async fn get_by_username(username: &str) -> Result { + let row = sqlx::query_as::<_, UserRow>(USER_BY_USERNAME_QUERY) + .bind(username) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + row.try_into().map_err(OmegaError::from) +} + +pub async fn get_by_user_id(id: UserId) -> Result { + let row = sqlx::query_as::<_, UserRow>(USER_BY_ID_QUERY) + .bind(id.0) + .fetch_optional(&pool().await?) + .await? + .ok_or(OmegaError::NotFound)?; + row.try_into().map_err(OmegaError::from) +} + +pub async fn get_users_by_iota_id(id: IotaId) -> Result> { + let rows = sqlx::query_as::<_, UserRow>(USERS_BY_IOTA_ID_QUERY) + .bind(id.0) + .fetch_all(&pool().await?) + .await?; + rows.into_iter() + .map(|row| row.try_into().map_err(OmegaError::from)) + .collect() +} + +async fn update( + id: UserId, + query: &'static str, + value: impl Send + sqlx::Encode<'static, sqlx::MySql> + sqlx::Type + 'static, +) -> Result<()> { + sqlx::query(query) + .bind(value) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn change_username(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET username = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_display_name(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET display = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_avatar(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET avatar = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_about(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET about = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn change_status(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET status = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} + +pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> { + sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") + .bind(value.0) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} +pub async fn change_token(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET token = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} +pub async fn delete_user(id: UserId) -> Result<()> { + sqlx::query("DELETE FROM users WHERE id = ?") + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> { + sqlx::query("UPDATE users SET public_key = ? WHERE id = ?") + .bind(public_key.as_bytes()) + .bind(id.0) + .execute(&pool().await?) + .await?; + Ok(()) +} + +pub async fn register_complete_user( + id: UserId, + username: String, + public_key: PublicKeyBundle, + iota_id: IotaId, + token: String, +) -> Result<()> { + sqlx::query( + "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", + ) + .bind(id.0) + .bind(username.into_bytes()) + .bind(public_key.as_bytes()) + .bind(iota_id.0) + .bind(token.into_bytes()) + .execute(&pool().await?) + .await?; + Ok(()) +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..9911d25 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,57 @@ +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum OmegaError { + #[error("database pool is not initialized")] + DatabaseNotInitialized, + #[error("database error: {0}")] + Database(sqlx::Error), + #[error("invalid input: {0}")] + Validation(String), + #[error("resource not found")] + NotFound, + #[error("transport error: {0}")] + Transport(String), + #[error("not connected")] + NotConnected, + #[error("not authenticated")] + NotAuthenticated, + #[error("invalid response")] + InvalidResponse, + #[error("authentication failed")] + AuthenticationFailed, + #[error("send error: {0}")] + SendError(String), + #[error("I/O error: {0}")] + Io(#[from] std::io::Error), +} + +pub type Result = std::result::Result; + +impl From for OmegaError { + fn from(error: sqlx::Error) -> Self { + if matches!(error, sqlx::Error::RowNotFound) { + Self::NotFound + } else { + Self::Database(error) + } + } +} + +impl OmegaError { + pub fn status_code(&self) -> http::StatusCode { + match self { + Self::Validation(_) => http::StatusCode::BAD_REQUEST, + Self::NotFound => http::StatusCode::NOT_FOUND, + Self::DatabaseNotInitialized + | Self::Database(_) + | Self::Transport(_) + | Self::NotConnected + | Self::NotAuthenticated + | Self::InvalidResponse + | Self::AuthenticationFailed + | Self::SendError(_) + | Self::Io(_) => http::StatusCode::INTERNAL_SERVER_ERROR, + } + } +} diff --git a/src/main.rs b/src/main.rs index 9e35297..251713e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,41 @@ +mod api; +mod config; +mod db; +pub mod error; +mod models; mod server; mod sql; mod transport; mod util; -use crate::sql::sql::initialize_db; -use crate::sql::sql::print_users; +pub use error::{OmegaError, Result}; + +use crate::db::initialize; use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; use dotenv::from_path; -use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; use mtp::crypto::Keyring; +use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; +use std::time::Duration; +use tokio::time::interval; const KEYRING_PATH: &str = "./omega.mk"; static KEYRING: Lazy = Lazy::new(|| { load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| { let kr = Keyring::generate(); - save_keyring_raw(&kr, KEYRING_PATH).expect("Failed to save generated keyring"); - save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) - .expect("Failed to save generated public key bundle"); + if let Err(error) = save_keyring_raw(&kr, KEYRING_PATH) { + eprintln!("Failed to save generated keyring: {error}"); + } + if let Err(error) = save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) { + eprintln!("Failed to save generated public key bundle: {error}"); + } eprintln!("Generated new keyring at {}", KEYRING_PATH); kr }) @@ -34,7 +45,10 @@ pub fn get_keyring() -> &'static Keyring { &KEYRING } pub fn load_keyring() -> Keyring { - Keyring::from_bytes(&KEYRING.to_bytes()).unwrap() + Keyring::from_bytes(&KEYRING.to_bytes()).unwrap_or_else(|error| { + eprintln!("Failed to clone keyring: {error}"); + Keyring::generate() + }) } #[tokio::main] @@ -50,7 +64,7 @@ async fn main() { log!("Started"); log!(" .env"); - if let Err(e) = initialize_db().await { + if let Err(e) = initialize().await { log!("[FATAL] Database initialization failed: {}", e); log!( "[FATAL] Please ensure the database is running and the .env file is configured correctly." @@ -59,12 +73,21 @@ async fn main() { } else { log!(" DB"); } - if let Err(e) = print_users().await { - log!("[ERROR] Failed to print users: {}", e); - } else { - log!(" Users"); - } - + let rate_limit_cleanup = crate::server::middleware::spawn_cleanup_task(); + let short_link_cleanup = tokio::spawn(async { + let mut ticker = interval(Duration::from_secs(24 * 60 * 60)); + loop { + ticker.tick().await; + if let Err(error) = crate::db::short_link_repo::delete_expired().await { + log_err!( + 0, + PrintType::General, + "Short-link cleanup failed: {}", + error + ); + } + } + }); let port: u16 = env::var("PORT") .ok() .and_then(|s| s.parse().ok()) @@ -80,4 +103,6 @@ async fn main() { log!("Shutting down on signal..."); } } + rate_limit_cleanup.abort(); + short_link_cleanup.abort(); } diff --git a/src/models/ids.rs b/src/models/ids.rs new file mode 100644 index 0000000..7eda84f --- /dev/null +++ b/src/models/ids.rs @@ -0,0 +1,30 @@ +use std::fmt::{Display, Formatter}; + +macro_rules! id_type { + ($name:ident) => { + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize)] + pub struct $name(pub i64); + + impl From for $name { + fn from(value: i64) -> Self { + Self(value) + } + } + + impl From<$name> for i64 { + fn from(value: $name) -> Self { + value.0 + } + } + + impl Display for $name { + fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(formatter) + } + } + }; +} + +id_type!(UserId); +id_type!(IotaId); +id_type!(OmikronId); diff --git a/src/models/iota.rs b/src/models/iota.rs new file mode 100644 index 0000000..3a93bcb --- /dev/null +++ b/src/models/iota.rs @@ -0,0 +1,9 @@ +use super::IotaId; +use mtp::crypto::PublicKeyBundle; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct Iota { + pub id: IotaId, + #[serde(serialize_with = "crate::models::serialize_public_key")] + pub public_key: PublicKeyBundle, +} diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..291729d --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,21 @@ +mod ids; +mod iota; +mod notification; +mod omikron; +mod user; + +fn serialize_public_key( + key: &mtp::crypto::PublicKeyBundle, + serializer: S, +) -> std::result::Result +where + S: serde::Serializer, +{ + serializer.serialize_str(&key.to_base64()) +} + +pub use ids::{IotaId, OmikronId, UserId}; +pub use iota::Iota; +pub use notification::Notification; +pub use omikron::Omikron; +pub use user::User; diff --git a/src/models/notification.rs b/src/models/notification.rs new file mode 100644 index 0000000..8dee06e --- /dev/null +++ b/src/models/notification.rs @@ -0,0 +1,9 @@ +use super::UserId; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct Notification { + pub id: i64, + pub sender_id: UserId, + pub receiver_id: UserId, + pub amount: i64, +} diff --git a/src/models/omikron.rs b/src/models/omikron.rs new file mode 100644 index 0000000..3b47d44 --- /dev/null +++ b/src/models/omikron.rs @@ -0,0 +1,12 @@ +use super::OmikronId; +use mtp::crypto::PublicKeyBundle; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct Omikron { + pub id: OmikronId, + #[serde(serialize_with = "crate::models::serialize_public_key")] + pub public_key: PublicKeyBundle, + pub location: String, + pub ip_address: String, + pub port: u16, +} diff --git a/src/models/user.rs b/src/models/user.rs new file mode 100644 index 0000000..0dda84c --- /dev/null +++ b/src/models/user.rs @@ -0,0 +1,19 @@ +use super::{IotaId, UserId}; +use mtp::crypto::PublicKeyBundle; + +#[derive(Clone, Debug, serde::Serialize)] +pub struct User { + pub id: UserId, + pub iota_id: IotaId, + pub username: String, + pub display: Option, + pub status: Option, + pub about: Option, + pub avatar: Option>, + pub sub_level: i32, + pub sub_end: i64, + #[serde(serialize_with = "crate::models::serialize_public_key")] + pub public_key: PublicKeyBundle, + #[serde(skip_serializing)] + pub token: String, +} diff --git a/src/server/api.rs b/src/server/api.rs index 6471398..6dc01aa 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,356 +1,240 @@ +use crate::api::response::{ + ConnectionsResponse, IotaResponse, OmikronResponse, PublicKeyResponse, StatusResponse, + UserResponse, UsernameResponse, json, +}; +use crate::db::{ + iota_repo::get_iota_by_id, + omikron_repo::get_omikron_by_id, + user_repo::{get_by_user_id, get_by_username}, +}; +use crate::error::{OmegaError, Result}; use crate::load_keyring; -use crate::sql::sql; -use crate::sql::sql::{get_by_user_id, get_iota_by_id, get_omikron_by_id}; +use crate::models::UserId; +use crate::server::{ + middleware, + validation::{parse_positive_id, validate_non_empty}, +}; use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use base64::Engine as _; use bytes::Bytes; use http::{Method, StatusCode}; -use json::JsonValue; use mtp::webserver::{Http3Request, Http3Response, RouteParams}; +use std::collections::BTreeMap; + +fn error_body(error: &OmegaError) -> String { + json(&StatusResponse { + status: match error { + OmegaError::Validation(_) => "error_bad_request", + OmegaError::NotFound => "error_not_found", + _ => "error", + }, + }) +} + +fn user_response(user: crate::models::User) -> UserResponse { + UserResponse { + status: "success", + username: user.username, + public_key: user.public_key.to_base64(), + user_id: user.id.0, + iota_id: user.iota_id.0, + sub_level: user.sub_level, + sub_end: user.sub_end, + display: user.display, + status_message: user.status, + about: user.about, + avatar: user + .avatar + .map(|value| base64::engine::general_purpose::STANDARD.encode(value)), + } +} + +async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { + match path_parts { + ["api", "get", "omikron"] => { + let connection = get_random_omikron() + .await + .map_err(|_| OmegaError::NotFound)?; + let id = connection + .get_omikron_id() + .await + .ok_or(OmegaError::NotFound)?; + let omikron = get_omikron_by_id(id.into()).await?; + Ok(( + StatusCode::OK, + json(&OmikronResponse { + status: "success", + id, + public_key: omikron.public_key.to_base64(), + ip_address: omikron.ip_address, + port: omikron.port, + }), + )) + } + ["api", "get", "omikron", id] => { + let id = parse_positive_id(id)?; + let omikron = match get_omikron_by_id(id.into()).await { + Ok(value) => value, + Err(_) => { + let fallback_id = + if let Some(fallback_id) = get_iota_primary_omikron_connection(id) { + fallback_id + } else { + let user = get_by_user_id(UserId::from(id)).await?; + get_iota_primary_omikron_connection(user.iota_id.0) + .ok_or(OmegaError::NotFound)? + }; + get_omikron_by_id(fallback_id.into()).await? + } + }; + Ok(( + StatusCode::OK, + json(&OmikronResponse { + status: "success", + id: omikron.id.0, + public_key: omikron.public_key.to_base64(), + ip_address: omikron.ip_address, + port: omikron.port, + }), + )) + } + ["api", "get", "connections"] => { + let connections = get_all_connections() + .await + .map_err(|_| OmegaError::Transport("failed to load connections".to_string()))?; + let connections = connections + .into_iter() + .map(|(omikron_id, iotas)| { + let iotas = iotas + .into_iter() + .map(|(iota_id, users)| { + ( + iota_id.to_string(), + users.into_iter().map(i64::from).collect(), + ) + }) + .collect(); + (omikron_id.to_string(), iotas) + }) + .collect::>(); + Ok(( + StatusCode::OK, + json(&ConnectionsResponse { + status: "success", + connections, + }), + )) + } + ["api", "get", "iota", id] => { + let id = parse_positive_id(id)?; + let iota = get_iota_by_id(id.into()).await?; + Ok(( + StatusCode::OK, + json(&IotaResponse { + status: "success", + iota_id: iota.id.0, + public_key: iota.public_key.to_base64(), + }), + )) + } + ["api", "get", "id", username] => { + validate_non_empty(username, "username", 15)?; + let user = get_by_username(username).await?; + Ok(( + StatusCode::OK, + json(&UsernameResponse { + status: "success", + username: user.username, + public_key: user.public_key.to_base64(), + user_id: user.id.0, + iota_id: user.iota_id.0, + sub_level: user.sub_level, + sub_end: user.sub_end, + }), + )) + } + ["api", "get", "public_key"] => { + let public_key = base64::engine::general_purpose::STANDARD + .encode(load_keyring().public_key_bundle().as_bytes()); + Ok(( + StatusCode::OK, + json(&PublicKeyResponse { + status: "success", + public_key, + }), + )) + } + ["api", "get", "user", id] => { + let id = parse_positive_id(id)?; + let user = get_by_user_id(UserId::from(id)).await?; + Ok((StatusCode::OK, json(&user_response(user)))) + } + _ => Ok(( + StatusCode::INTERNAL_SERVER_ERROR, + json(&StatusResponse { status: "error" }), + )), + } +} pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Response { let method = request.method; let path = request.uri.path().to_string(); - let body_string = request - .body - .map(|body| String::from_utf8_lossy(&body).to_string()); - + if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { + return response + .status(StatusCode::TOO_MANY_REQUESTS) + .header("access-control-allow-origin", &crate::config::cors_origin()) + .body(json(&StatusResponse { + status: "error_rate_limited", + })); + } if method == Method::OPTIONS { return response .status(StatusCode::OK) - .header("access-control-allow-origin", "*") + .header("access-control-allow-origin", &crate::config::cors_origin()) .header("access-control-allow-methods", "GET, POST, OPTIONS") .header("access-control-allow-headers", "*"); } - - let path_parts: Vec<&str> = path.split('/').filter(|s| !s.is_empty()).collect(); - - let _body: Option = if let Some(ref bs) = body_string { - if let Ok(body_json) = json::parse(bs) { - Some(body_json) - } else { - None - } - } else { - None - }; - - let (status, body_text) = match path_parts.as_slice() { - // ================================================== - // DOWNLOAD IOTA FRONTEND - // ================================================== - ["api", "download", "iota_frontend"] => { - let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); - - match std::fs::read(file_path) { - Ok(file_bytes) => { - return response - .status(StatusCode::OK) - .header("access-control-allow-origin", "*") - .header("content-type", "application/zip") - .header( - "content-disposition", - "attachment; filename=\"iota_frontend.zip\"", - ) - .body(Bytes::from(file_bytes)); - } - Err(_) => { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - return response - .status(StatusCode::NOT_FOUND) - .header("access-control-allow-origin", "*") - .body(res.dump()); - } - } - } - - // ================================================== - // GET RANDOM OMIKRON - // ================================================== - ["api", "get", "omikron"] => { - if let Ok(omikron_conn) = get_random_omikron().await { - if let Some(id) = omikron_conn.get_omikron_id().await { - if let Ok((public_key, ip_address, port)) = sql::get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET OMIKRON BY ID - // ================================================== - ["api", "get", "omikron", id] => { - let id = id.parse::().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((public_key, ip_address, port)) = get_omikron_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - (StatusCode::OK, res.dump()) - } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { - if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = get_by_user_id(id).await - { - if let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) { - if let Ok((public_key, ip_address, port)) = get_omikron_by_id(omikron_id).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["id"] = omikron_id.into(); - res["public_key"] = public_key.to_base64().into(); - res["ip_address"] = ip_address.into(); - res["port"] = port.into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - ["api", "get", "connections"] => { - if let Ok(connections) = get_all_connections().await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - - for (omikron_id, iota_map) in connections { - let mut omikron_obj = JsonValue::new_object(); - for (iota_id, user_ids) in iota_map { - let mut user_arr = JsonValue::new_array(); - for user_id in user_ids { - let _ = user_arr.push(user_id); - } - omikron_obj[&iota_id.to_string()] = user_arr; - } - res[&omikron_id.to_string()] = omikron_obj; - } - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - } - - // ================================================== - // GET IOTA BY ID - // ================================================== - ["api", "get", "iota", id] => { - let id: i64 = id.parse().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok((id, public_key)) = get_iota_by_id(id).await { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["iota_id"] = id.into(); - res["public_key"] = public_key.to_base64().into(); - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::NOT_FOUND, res.dump()) - } - } - - // ================================================== - // GET ID BY USERNAME - // ================================================== - ["api", "get", "id", username] => { - if username.is_empty() { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - _, - _, - _, - _, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_username(username).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.to_base64().into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // GET SERVER PUBLIC KEY - // ================================================== - ["api", "get", "public_key"] => { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - let bundle = load_keyring().public_key_bundle(); - res["public_key"] = base64::engine::general_purpose::STANDARD - .encode(bundle.as_bytes()) - .into(); - (StatusCode::OK, res.dump()) - } - - // ================================================== - // GET USER BY ID - // ================================================== - ["api", "get", "user", id] => { - let id: i64 = id.parse().unwrap_or(0); - - if id == 0 { - let mut res = JsonValue::new_object(); - res["status"] = "error_bad_request".into(); - (StatusCode::BAD_REQUEST, res.dump()) - } else if let Ok(( - id, - iota_id, - username, - display, - status_msg, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - )) = sql::get_by_user_id(id).await - { - let mut res = JsonValue::new_object(); - res["status"] = "success".into(); - res["username"] = username.into(); - res["public_key"] = public_key.to_base64().into(); - res["user_id"] = id.into(); - res["iota_id"] = iota_id.into(); - res["sub_level"] = sub_level.into(); - res["sub_end"] = sub_end.into(); - - if let Some(display) = display { - res["display"] = display.into(); - } - if let Some(status_msg) = status_msg { - res["status_message"] = status_msg.into(); - } - if let Some(about) = about { - res["about"] = about.into(); - } - if let Some(avatar) = avatar { - res["avatar"] = base64::engine::general_purpose::STANDARD - .encode(avatar) - .into(); - } - - (StatusCode::OK, res.dump()) - } else { - let mut res = JsonValue::new_object(); - res["status"] = "error_not_found".into(); - (StatusCode::OK, res.dump()) - } - } - - // ================================================== - // DIRECT - SHORT LINK RESOLUTION - // ================================================== - ["direct", short @ ..] => { - let short_str = short.join("/"); - let short = short_str.replace("/", ""); - if let Ok(long) = crate::server::short_link::get_short_link(&short).await { - return response - .status(StatusCode::TEMPORARY_REDIRECT) - .header("location", &long); - } else { - return response - .status(StatusCode::TEMPORARY_REDIRECT) - .header("location", "https://tensamin.net"); - } - } - - // ================================================== - // DEFAULT - // ================================================== - _ => { - let mut res = JsonValue::new_object(); - res["status"] = "error".into(); - (StatusCode::INTERNAL_SERVER_ERROR, res.dump()) - } - }; - + let path_parts: Vec<&str> = path.split('/').filter(|part| !part.is_empty()).collect(); + if let ["api", "download", "iota_frontend"] = path_parts.as_slice() { + let file_path = format!("{}/downloads/iota_frontend.zip", get_directory()); + return match std::fs::read(file_path) { + Ok(bytes) => response + .status(StatusCode::OK) + .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("content-type", "application/zip") + .header( + "content-disposition", + "attachment; filename=\"iota_frontend.zip\"", + ) + .body(Bytes::from(bytes)), + Err(_) => response + .status(StatusCode::NOT_FOUND) + .header("access-control-allow-origin", &crate::config::cors_origin()) + .body(json(&StatusResponse { + status: "error_not_found", + })), + }; + } + if let ["direct", short @ ..] = path_parts.as_slice() { + let short = short.join(""); + let location = crate::server::short_link::get_short_link(&short) + .await + .unwrap_or_else(|_| "https://tensamin.net".to_string()); + return response + .status(StatusCode::TEMPORARY_REDIRECT) + .header("location", &location); + } + let (status, body) = route(&path_parts) + .await + .unwrap_or_else(|error| (error.status_code(), error_body(&error))); response .status(status) - .header("access-control-allow-origin", "*") + .header("access-control-allow-origin", &crate::config::cors_origin()) .header("access-control-allow-headers", "*") .header("access-control-allow-methods", "GET, POST, OPTIONS") - .body(body_text) + .body(body) } pub async fn handle_pattern( diff --git a/src/server/middleware.rs b/src/server/middleware.rs new file mode 100644 index 0000000..5f373ec --- /dev/null +++ b/src/server/middleware.rs @@ -0,0 +1,84 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::net::IpAddr; +use std::{collections::VecDeque, time::Instant}; +use tokio::time::interval; + +static REQUESTS: Lazy>> = Lazy::new(DashMap::new); +static CONFIG: Lazy = + Lazy::new(crate::config::RateLimitConfig::from_env); +const MAX_TRACKED_CLIENT_BUCKETS: usize = 100_000; + +pub fn allow(remote_addr: IpAddr, path: &str) -> bool { + let key = if path.contains("register") { + "registration" + } else { + "general" + }; + let map_key = (remote_addr, key.to_string()); + if REQUESTS.len() >= MAX_TRACKED_CLIENT_BUCKETS { + cleanup_expired(); + if REQUESTS.len() >= MAX_TRACKED_CLIENT_BUCKETS && !REQUESTS.contains_key(&map_key) { + return false; + } + } + let limit = if key == "registration" { + CONFIG.registration_requests + } else { + CONFIG.general_requests + }; + let now = Instant::now(); + let mut entries = REQUESTS.entry(map_key).or_default(); + while entries + .front() + .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + { + entries.pop_front(); + } + if entries.len() >= limit { + return false; + } + entries.push_back(now); + true +} + +pub fn spawn_cleanup_task() -> tokio::task::JoinHandle<()> { + tokio::spawn(async { + let mut ticker = interval(CONFIG.window); + loop { + ticker.tick().await; + cleanup_expired(); + } + }) +} + +fn cleanup_expired() { + let now = Instant::now(); + REQUESTS.retain(|_, entries| { + while entries + .front() + .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + { + entries.pop_front(); + } + !entries.is_empty() + }); +} + +#[cfg(test)] +mod tests { + use super::allow; + use std::net::{IpAddr, Ipv4Addr}; + + #[test] + fn tracks_clients_independently() { + let first: IpAddr = "192.0.2.10" + .parse() + .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); + let second: IpAddr = "192.0.2.11" + .parse() + .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); + assert!(allow(first, "/api/get/user/1")); + assert!(allow(second, "/api/get/user/1")); + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 59e9769..2d01c5b 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,4 +1,6 @@ pub mod api; pub mod index; +pub mod middleware; pub mod server; pub mod short_link; +pub mod validation; diff --git a/src/server/short_link.rs b/src/server/short_link.rs index b3f91bd..dd24ebf 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -1,37 +1,27 @@ -use dashmap::DashMap; -use once_cell::sync::Lazy; -use rand::{Rng, thread_rng}; - -static LINKS: Lazy> = Lazy::new(DashMap::new); +use crate::db::short_link_repo; +use rand::RngExt; const CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHJKLMNPRSTUVWXYZ1234567890"; pub async fn add_short_link(long: &str) -> Result { - let raw = generate_unique_short_link().await; - LINKS.insert(raw.clone(), long.to_string()); - - Ok(format!( - "hmtps://omega.tensamin.net/direct/{}", - format_with_dashes(&raw) - )) -} - -async fn generate_unique_short_link() -> String { loop { - let short = generate_short_link().await; - if !LINKS.contains_key(&short) { - return short; + let raw = generate_short_link().await; + if short_link_repo::insert(&raw, long).await.map_err(|_| ())? { + return Ok(format!( + "https://omega.tensamin.net/direct/{}", + format_with_dashes(&raw) + )); } } } pub async fn generate_short_link() -> String { - let len = short_length(); + let len = short_length().await; - let mut rng = thread_rng(); + let mut rng = rand::rng(); (0..len) .map(|_| { - let idx = rng.gen_range(0..CHARSET.len()); + let idx = rng.random_range(0..CHARSET.len()); CHARSET[idx] as char }) .collect() @@ -46,17 +36,17 @@ pub async fn get_short_link(short: &str) -> Result { let frag = short.replace(key, ""); let normalized = normalize_short(&key); - if let Ok(t) = LINKS.get(&normalized).map(|v| v.value().clone()).ok_or(()) { - Ok(format!("{}{}", t, frag)) - } else { - Err(()) - } + let target = short_link_repo::get(&normalized) + .await + .map_err(|_| ())? + .ok_or(())?; + Ok(format!("{}{}", target, frag)) } /* ---------------- helpers ---------------- */ -fn short_length() -> usize { - let count = LINKS.len(); +async fn short_length() -> usize { + let count = short_link_repo::count().await.unwrap_or(0); match count { 0..=1_999 => 4, diff --git a/src/server/validation.rs b/src/server/validation.rs new file mode 100644 index 0000000..b80799c --- /dev/null +++ b/src/server/validation.rs @@ -0,0 +1,42 @@ +use crate::error::{OmegaError, Result}; + +pub fn parse_positive_id(value: &str) -> Result { + let id = value + .parse::() + .map_err(|_| OmegaError::Validation("ID must be a positive integer".to_string()))?; + if id <= 0 { + return Err(OmegaError::Validation( + "ID must be a positive integer".to_string(), + )); + } + Ok(id) +} + +pub fn validate_non_empty(value: &str, field: &str, max_len: usize) -> Result<()> { + let length = value.chars().count(); + if length == 0 || length > max_len { + return Err(OmegaError::Validation(format!( + "{field} must contain 1 to {max_len} characters" + ))); + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::{parse_positive_id, validate_non_empty}; + + #[test] + fn rejects_invalid_ids() { + assert!(parse_positive_id("bad").is_err()); + assert!(parse_positive_id("0").is_err()); + assert_eq!(parse_positive_id("7").expect("valid test ID"), 7); + } + + #[test] + fn enforces_string_bounds() { + assert!(validate_non_empty("", "username", 15).is_err()); + assert!(validate_non_empty("abcdefghijklmnop", "username", 15).is_err()); + assert!(validate_non_empty("alice", "username", 15).is_ok()); + } +} diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 91ba841..bec658c 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -1,3 +1,2 @@ pub mod connection_status; -pub mod sql; pub mod user_online_tracker; diff --git a/src/sql/sql.rs b/src/sql/sql.rs deleted file mode 100644 index 91cd5ba..0000000 --- a/src/sql/sql.rs +++ /dev/null @@ -1,778 +0,0 @@ -use crate::log; -use mtp::crypto::PublicKeyBundle; -use once_cell::sync::Lazy; -use sqlx::{MySql, Pool, Row, mysql::MySqlPoolOptions}; -use std::sync::atomic::{AtomicU64, Ordering}; -use std::{ - env, - sync::Arc, - time::{SystemTime, UNIX_EPOCH}, -}; -use tokio::sync::RwLock; - -/* -use crate::sql::{ - iota_omikron_tracker::{get_omikron_for_iota, track_iota_omikron, untrack_iota}, - sql::{ - change_about, change_avatar, change_display_name, change_iota_id, change_iota_key, - change_keys, change_status, change_username, get_by_id, get_by_username, get_iota_by_id, - get_register_id, register_complete_iota, register_complete_user, - }, -}; - */ - -static SQL_DB: Lazy>>>> = Lazy::new(|| Arc::new(RwLock::new(None))); - -pub async fn connect() -> Result, sqlx::Error> { - let url = env::var("DB_URL").expect("DB_URL is not set"); - - MySqlPoolOptions::new() - .max_connections(200) - .connect(&url) - .await -} -// Omega -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -// - Omikron -// - Iota -// - User -// - User -// - Iota -// - User -// - User -pub async fn initialize_db() -> Result<(), sqlx::Error> { - let pool = connect().await?; - let mut db_lock = SQL_DB.write().await; - // create tables - // with indexes - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - users ( - id BIGINT NOT NULL PRIMARY KEY, - username VARCHAR(15) NOT NULL UNIQUE COLLATE utf8mb4_bin, - display VARCHAR(15) COLLATE utf8mb4_bin, - status VARCHAR(15) COLLATE utf8mb4_bin, - about VARCHAR(200) COLLATE utf8mb4_bin, - avatar MEDIUMBLOB, - sub_level INT(11) NOT NULL DEFAULT 0, - sub_end BIGINT(20) NOT NULL DEFAULT 0, - public_key BLOB NOT NULL, - private_key_hash TEXT NOT NULL COLLATE utf8mb4_bin DEFAULT '', - iota_id BIGINT NOT NULL, - token VARCHAR(256) NOT NULL UNIQUE COLLATE utf8mb4_bin - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - iotas ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL - )", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - omikrons ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL, - location VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - ip_address VARCHAR(255) NOT NULL COLLATE utf8mb4_bin, - port INT(11) NOT NULL DEFAULT 959 - )", - ) - .execute(&pool) - .await; - // Retrofits `port` onto omikrons tables created before this column existed; - // `CREATE TABLE IF NOT EXISTS` above is a no-op against an already-existing table. - let _ = sqlx::query( - "ALTER TABLE omikrons ADD COLUMN IF NOT EXISTS port INT(11) NOT NULL DEFAULT 959", - ) - .execute(&pool) - .await; - let _ = sqlx::query( - "CREATE TABLE IF NOT EXISTS - notifications ( - id BIGINT NOT NULL PRIMARY KEY AUTO_INCREMENT, - sender_id BIGINT NOT NULL, - receiver_id BIGINT NOT NULL, - amount BIGINT NOT NULL DEFAULT 0 - )", - ) - .execute(&pool) - .await; - *db_lock = Some(pool); - Ok(()) -} - -// ========================================================================================== -// REGISTER -// ========================================================================================== -pub static CURRENT_MILLI_USED: Lazy> = Lazy::new(|| Arc::new(AtomicU64::new(0))); -pub static CURRENT_REGISTER_PROCESS: Lazy>>> = - Lazy::new(|| Arc::new(RwLock::new(Vec::new()))); - -pub async fn get_register_id() -> u64 { - let mut current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap_or_default() - .as_millis() as u64; - - loop { - let current_locked = CURRENT_MILLI_USED.load(Ordering::SeqCst); - - if current_locked < current_time { - let result = CURRENT_MILLI_USED.compare_exchange( - current_locked, // expected value - current_time, // new value - Ordering::SeqCst, // acquire/release ordering - Ordering::SeqCst, // failure ordering - ); - - match result { - Ok(_) => { - CURRENT_REGISTER_PROCESS.write().await.push(current_time); - return current_time; - } - Err(_) => { - continue; - } - } - } else { - current_time = current_locked + 1; - } - } -} - -// ========================================================================================== -// USERS -// ========================================================================================== - -pub async fn get_by_username( - username: &str, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE username = ?", - ) - .bind(username) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - let private_key_hash: Vec = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - String::from_utf8_lossy(&private_key_hash).to_string(), - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_by_user_id( - id: i64, -) -> Result< - ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - ), - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE id = ?", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some(row) => { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - let private_key_hash: Vec = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - Ok(( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - String::from_utf8_lossy(&private_key_hash).to_string(), - String::from_utf8_lossy(&token).to_string(), - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -pub async fn get_users_by_iota_id( - iota_id_param: i64, -) -> Result< - Vec<( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - )>, - sqlx::Error, -> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let rows = sqlx::query( - "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, private_key_hash, token FROM users WHERE iota_id = ?", - ) - .bind(iota_id_param) - .fetch_all(&pool) - .await?; - - let mut users = Vec::new(); - for row in rows { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let avatar: Option> = row.get("avatar"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - let public_key = PublicKeyBundle::from_bytes(&row.get::, _>("public_key")) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - let private_key_hash: Vec = row.get("private_key_hash"); - let token: Vec = row.get("token"); - - users.push(( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map(|d| String::from_utf8_lossy(&d).to_string()), - status.map(|s| String::from_utf8_lossy(&s).to_string()), - about.map(|a| String::from_utf8_lossy(&a).to_string()), - avatar, - sub_level, - sub_end, - public_key, - String::from_utf8_lossy(&private_key_hash).to_string(), - String::from_utf8_lossy(&token).to_string(), - )); - } - - Ok(users) -} - -pub async fn change_username(id: i64, new_username: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET username = ? WHERE id = ?") - .bind(new_username.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_display_name(id: i64, new_display: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET display = ? WHERE id = ?") - .bind(new_display.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_avatar(id: i64, new_avatar: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET avatar = ? WHERE id = ?") - .bind(new_avatar.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_about(id: i64, new_about: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET about = ? WHERE id = ?") - .bind(new_about.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_status(id: i64, new_status: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET status = ? WHERE id = ?") - .bind(new_status.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_user(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM users WHERE id = ?") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_iota_id(id: i64, new_iota_id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") - .bind(new_iota_id) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn change_keys( - id: i64, - new_public_key: PublicKeyBundle, - new_private_key_hash: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET public_key = ?, private_key_hash = ? WHERE id = ?") - .bind(new_public_key.as_bytes()) - .bind(new_private_key_hash.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn change_token(id: i64, new_token: String) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE users SET token = ? WHERE id = ?") - .bind(new_token.as_bytes().to_vec()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn register_complete_user( - id: i64, - username: String, - public_key: PublicKeyBundle, - iota_id: i64, - token: String, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query( - "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", - ) - .bind(id) - .bind(username.as_bytes().to_vec()) - .bind(public_key.as_bytes()) - .bind(iota_id) - .bind(token.as_bytes().to_vec()) - .execute(&pool) - .await?; - - Ok(()) -} -pub async fn print_users() -> Result<(), Box> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - log!("Printing users..."); - for row in sqlx::query( - "SELECT id, iota_id, username, display, status, about, sub_level, sub_end, public_key, private_key_hash, token FROM users", - ) - .fetch_all(&pool) - .await? - .iter() - { - let id: i64 = row.get("id"); - let iota_id: i64 = row.get("iota_id"); - let username: Vec = row.get("username"); - let display: Option> = row.get("display"); - let status: Option> = row.get("status"); - let about: Option> = row.get("about"); - let sub_level: i32 = row.get("sub_level"); - let sub_end: i64 = row.get("sub_end"); - - log!( - "User: {:?}", - ( - id, - iota_id, - String::from_utf8_lossy(&username).to_string(), - display.map_or("".to_string(), |d| String::from_utf8_lossy(&d).to_string()), - status.map_or("".to_string(), |s| String::from_utf8_lossy(&s).to_string()), - about.map_or("".to_string(), |a| String::from_utf8_lossy(&a).to_string()), - sub_level, - sub_end - ) - ); - } - - Ok(()) -} - -// ========================================================================================== -// IOTA -// ========================================================================================== -pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { - let new_id = get_register_id().await as i64; - register_complete_iota(new_id, public_key).await?; - Ok(new_id) -} - -pub async fn register_complete_iota( - id: i64, - public_key: PublicKeyBundle, -) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") - .bind(id) - .bind(public_key.as_bytes()) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn get_iota_by_id(id: i64) -> Result<(i64, PublicKeyBundle), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let result = - sqlx::query_as::<_, (i64, Vec)>("SELECT id, public_key FROM iotas WHERE id = ?") - .bind(id) - .fetch_optional(&pool) - .await; - - match result { - Ok(optional_row) => match optional_row { - Some((id_i64, public_key)) => { - let bundle = PublicKeyBundle::from_bytes(&public_key) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - Ok((id_i64, bundle)) - } - _ => Err(sqlx::Error::RowNotFound), - }, - Err(e) => Err(e), - } -} - -pub async fn change_iota_key(id: i64, new_key: PublicKeyBundle) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(new_key.as_bytes()) - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -pub async fn delete_iota(id: i64) -> Result<(), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - sqlx::query("DELETE FROM iotas WHERE id = ?") - .bind(id) - .execute(&pool) - .await?; - - Ok(()) -} - -// ========================================================================================== -// OMIKRONS -// ========================================================================================== - -pub async fn get_omikron_by_id(id: i64) -> Result<(PublicKeyBundle, String, u16), sqlx::Error> { - let pool = { - let db_lock = SQL_DB.read().await; - db_lock - .as_ref() - .cloned() - .expect("Database pool not initialized") - }; - - let row = sqlx::query_as::<_, (Vec, Vec, i32)>( - "SELECT public_key, ip_address, port FROM omikrons WHERE id = ?", - ) - .bind(id) - .fetch_optional(&pool) - .await?; - - match row { - Some((public_key, ip_address, port)) => { - let bundle = PublicKeyBundle::from_bytes(&public_key) - .map_err(|e| sqlx::Error::Decode(Box::new(e)))?; - Ok(( - bundle, - String::from_utf8_lossy(&ip_address).to_string(), - port as u16, - )) - } - _ => Err(sqlx::Error::RowNotFound), - } -} - -// ========================================================================================== -// PHI -// ========================================================================================== - -pub async fn add_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - INSERT INTO notifications (sender_id, receiver_id, amount) - VALUES (?, ?, 1) - ON DUPLICATE KEY UPDATE amount = amount + 1 - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn read_notification(sender_id: i64, receiver_id: i64) -> Result<(), sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query( - r#" - DELETE FROM notifications WHERE sender_id = ? AND receiver_id = ? - "#, - ) - .bind(sender_id) - .bind(receiver_id) - .execute(pool) - .await?; - - Ok(()) -} -pub async fn get_notifications(user_id: i64) -> Result, sqlx::Error> { - let db_lock = SQL_DB.read().await; - let pool = db_lock.as_ref().expect("Database pool is not initialized"); - - sqlx::query_as::<_, (i64, i64)>( - r#" - SELECT sender_id, amount FROM notifications WHERE receiver_id = ? - "#, - ) - .bind(user_id) - .fetch_all(pool) - .await -} diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 288fcdc..a0c2cd8 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,4 +1,5 @@ -use crate::sql; +use crate::db::user_repo; +use crate::models::IotaId; use crate::sql::connection_status::UserStatus; use dashmap::DashMap; use once_cell::sync::Lazy; @@ -33,27 +34,30 @@ pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { } pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let connections_empty = if let Some(r) = IOTA_OMIKRON_CONNECTIONS.get(&iota_id) { - let mut vec = r.value().clone(); - vec.retain(|&id| id != omikron_id); - let empty = vec.is_empty(); - drop(r); - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, vec); - empty - } else { - false - }; - - if let Some(primary_ref) = IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id) { - let primary_id = *primary_ref.value(); - drop(primary_ref); - if primary_id == omikron_id { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } + let mut replacement = None; + let mut connections_empty = false; + if let Some(mut entry) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { + entry.retain(|&id| id != omikron_id); + connections_empty = entry.is_empty(); + replacement = entry.first().copied(); } if connections_empty { - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); + IOTA_OMIKRON_CONNECTIONS.remove_if(&iota_id, |_, connections| connections.is_empty()); + } + + if IOTA_PRIMARY_OMIKRON_CONNECTION + .get(&iota_id) + .is_some_and(|primary| *primary == omikron_id) + { + match replacement { + Some(omikron_id) => { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); + } + None => { + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + } + } } connections_empty @@ -87,9 +91,9 @@ pub async fn get_all_connections() let iota_id = *entry.key(); let omikron_ids = entry.value().clone(); - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { for user in users { - let user_id = user.0 as i64; + let user_id = user.id.0; if let Some(conn) = USER_STATUS_MAP.get(&user_id) { let user_omikron_id = conn.omikron_id; if omikron_ids.contains(&user_omikron_id) { @@ -117,6 +121,12 @@ pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { ); } +pub fn untrack_user_status(user_id: i64, omikron_id: i64) { + USER_STATUS_MAP.remove_if(&user_id, |_, connection| { + connection.omikron_id == omikron_id + }); +} + pub fn get_user_status(user_id: i64) -> Option { USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } @@ -128,57 +138,38 @@ pub fn untrack_many_users(user_ids: &[i64]) { } pub async fn untrack_omikron(omikron_id: i64) { - let primary_keys_to_remove: Vec = IOTA_PRIMARY_OMIKRON_CONNECTION - .iter() - .filter(|entry| *entry.value() == omikron_id) - .map(|entry| *entry.key()) - .collect(); - - for key in primary_keys_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&key); - } - let mut offline_iotas = Vec::new(); - let mut primary_to_remove = Vec::new(); + let mut primary_replacements = Vec::new(); - // Collect iotas and primary info first - for r in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *r.key(); - let mut connections = r.value().clone(); - connections.retain(|&id| id != omikron_id); - - if connections.is_empty() { + for mut entry in IOTA_OMIKRON_CONNECTIONS.iter_mut() { + let iota_id = *entry.key(); + entry.retain(|&id| id != omikron_id); + if entry.is_empty() { offline_iotas.push(iota_id); - } - - if IOTA_PRIMARY_OMIKRON_CONNECTION + } else if IOTA_PRIMARY_OMIKRON_CONNECTION .get(&iota_id) - .map(|p| *p == omikron_id) - .unwrap_or(false) + .is_some_and(|primary| *primary == omikron_id) { - primary_to_remove.push(iota_id); + primary_replacements.push((iota_id, entry[0])); } - - // Update the connections vector after filtering - IOTA_OMIKRON_CONNECTIONS.insert(iota_id, connections); } - // Step 2: Remove primary connections safely - for iota_id in primary_to_remove { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); + for iota_id in &offline_iotas { + IOTA_OMIKRON_CONNECTIONS.remove_if(iota_id, |_, connections| connections.is_empty()); + IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); + } + + for (iota_id, replacement) in primary_replacements { + IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, replacement); } - // Step 3: Remove users that were on this omikron USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - // Step 4: For offline iotas, remove associated users from USER_STATUS_MAP for iota_id in offline_iotas { - if let Ok(users) = sql::sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { + if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { for user in users { - USER_STATUS_MAP.remove(&(user.0 as i64)); + USER_STATUS_MAP.remove(&user.id.0); } } - // Finally remove the empty connections vector - IOTA_OMIKRON_CONNECTIONS.remove(&iota_id); } } diff --git a/src/transport/connection.rs b/src/transport/connection.rs new file mode 100644 index 0000000..eff0bb1 --- /dev/null +++ b/src/transport/connection.rs @@ -0,0 +1 @@ +pub(crate) use super::omikron_connection::OmikronConnection; diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs new file mode 100644 index 0000000..1927b14 --- /dev/null +++ b/src/transport/handlers/account.rs @@ -0,0 +1,43 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::{iota_repo, user_repo}, + models::{IotaId, UserId}, +}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use std::sync::Arc; + +async fn delete( + connection: Arc, + value: CommunicationValue, + result: impl std::future::Future>, +) -> OmikronResult<()> { + let response = match result.await { + Ok(()) => CommunicationValue::new(CommunicationType::Success), + Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + }; + connection.send(&response.with_id(value.get_id())).await +} + +pub async fn user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + delete( + connection, + value.clone(), + user_repo::delete_user(UserId::from(value.get_sender() as i64)), + ) + .await +} +pub async fn iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + delete( + connection, + value.clone(), + iota_repo::delete_iota(IotaId::from(value.get_sender() as i64)), + ) + .await +} diff --git a/src/transport/handlers/calls.rs b/src/transport/handlers/calls.rs new file mode 100644 index 0000000..9b16182 --- /dev/null +++ b/src/transport/handlers/calls.rs @@ -0,0 +1 @@ +/* Call and WebRTC commands are reserved for the transport extensions that define those protocol values. */ diff --git a/src/transport/handlers/links.rs b/src/transport/handlers/links.rs new file mode 100644 index 0000000..a43cb99 --- /dev/null +++ b/src/transport/handlers/links.rs @@ -0,0 +1,21 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::server::short_link::add_short_link; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use std::sync::Arc; + +pub async fn shorten( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let link = value + .get_data(DataType::Link) + .as_str() + .ok_or(crate::error::OmegaError::InvalidResponse)?; + let short = add_short_link(link) + .await + .map_err(|_| crate::error::OmegaError::Transport("short link error".to_string()))?; + let response = CommunicationValue::new(CommunicationType::ShortenLink) + .with_id(value.get_id()) + .add_typed_default(DataType::Link, DataValue::Str(short)); + connection.send(&response).await +} diff --git a/src/transport/handlers/messaging.rs b/src/transport/handlers/messaging.rs new file mode 100644 index 0000000..38c66d6 --- /dev/null +++ b/src/transport/handlers/messaging.rs @@ -0,0 +1 @@ +/* Message delivery remains in the connection dispatcher until the protocol exposes a separate message handler contract. */ diff --git a/src/transport/handlers/mod.rs b/src/transport/handlers/mod.rs new file mode 100644 index 0000000..cb506cf --- /dev/null +++ b/src/transport/handlers/mod.rs @@ -0,0 +1,9 @@ +pub mod account; +pub mod calls; +pub mod links; +pub mod messaging; +pub mod notifications; +pub mod presence; +pub mod register; +pub mod states; +pub mod user_data; diff --git a/src/transport/handlers/notifications.rs b/src/transport/handlers/notifications.rs new file mode 100644 index 0000000..5f23474 --- /dev/null +++ b/src/transport/handlers/notifications.rs @@ -0,0 +1,116 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{db::notification_repo, log, models::UserId}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::type_map::TypeMap; +use std::sync::Arc; + +pub async fn get( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let notifications = + match notification_repo::get_notifications(UserId::from(value.get_sender() as i64)).await { + Ok(items) => items + .into_iter() + .map(|item| { + let tm = TypeMap::latest(); + let Some(sender) = DataType::SenderId.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + let Some(amount) = DataType::Amount.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + DataValue::Container(vec![ + (sender, DataValue::SignedNumber(item.sender_id.0.into())), + (amount, DataValue::SignedNumber(item.amount.into())), + ]) + }) + .collect(), + Err(error) => { + log!( + crate::util::logger::PrintType::General, + "SQL get_notifications error: {}", + error + ); + Vec::new() + } + }; + let response = CommunicationValue::new(CommunicationType::GetNotifications) + .with_id(value.get_id()) + .add_typed_default(DataType::Notifications, DataValue::Array(notifications)); + connection.send(&response).await +} + +pub async fn read( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let receiver = match value.get_sender() { + sender if sender > 0 => sender as i64, + _ => match value.get_data(DataType::ReceiverId).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + let Some(other) = value + .get_data(DataType::SenderId) + .as_number() + .map(|id| id as i64) + else { + return Ok(()); + }; + if let Err(error) = + notification_repo::read_notification(UserId::from(receiver), UserId::from(other)).await + { + log!( + crate::util::logger::PrintType::General, + "SQL read_notification error: {}", + error + ); + } else { + let response = + CommunicationValue::new(CommunicationType::ReadNotification).with_id(value.get_id()); + let _ = connection.send(&response).await; + let sync = CommunicationValue::new(CommunicationType::ReadNotification) + .with_receiver(receiver as u64) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(other.into())); + crate::transport::omikron_manager::send_to_user(receiver, &sync).await; + } + Ok(()) +} + +pub async fn push( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let receiver = match value.get_receiver() { + receiver if receiver > 0 => receiver as i64, + _ => match value.get_data(DataType::ReceiverId).as_number() { + Some(id) => id as i64, + None => return Ok(()), + }, + }; + let sender = value + .get_data(DataType::SenderId) + .as_number() + .map(|id| id as i64) + .unwrap_or(value.get_sender() as i64); + if let Err(error) = + notification_repo::add_notification(UserId::from(receiver), UserId::from(sender)).await + { + log!( + crate::util::logger::PrintType::General, + "SQL add_notification error: {}", + error + ); + } else { + let response = + CommunicationValue::new(CommunicationType::PushNotification).with_id(value.get_id()); + let _ = connection.send(&response).await; + let push = CommunicationValue::new(CommunicationType::PushNotification) + .with_receiver(receiver as u64) + .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender.into())); + crate::transport::omikron_manager::send_to_user(receiver, &push).await; + } + Ok(()) +} diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs new file mode 100644 index 0000000..1dd10d0 --- /dev/null +++ b/src/transport/handlers/presence.rs @@ -0,0 +1,128 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::user_repo, + log_in, + models::IotaId, + sql::{connection_status::UserStatus, user_online_tracker}, +}; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use std::sync::Arc; + +pub async fn user_connected( + _connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User connected"); + if let Some(user_id) = value.get_data(DataType::UserId).as_number() { + let status = value + .get_data(DataType::UserState) + .as_str() + .and_then(UserStatus::from_str) + .unwrap_or(UserStatus::user_online); + if let Ok(user_id) = i64::try_from(user_id) { + user_online_tracker::track_user_status(user_id, status, omikron_id); + } + } + Ok(()) +} + +pub async fn user_disconnected( + _: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); + if let Some(user_id) = value.get_data(DataType::UserId).as_number() { + user_online_tracker::untrack_user_status(user_id as i64, omikron_id); + } + Ok(()) +} + +pub async fn iota_connected( + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "IOTA connected"); + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .map(|id| id as i64) + else { + return Ok(()); + }; + user_online_tracker::track_iota_connection(iota_id, omikron_id, true); + let mut user_ids = Vec::new(); + match user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { + Ok(users) => { + for user in users { + user_ids.push(DataValue::SignedNumber(user.id.0.into())); + user_online_tracker::track_user_status( + user.id.0, + UserStatus::user_offline, + omikron_id, + ); + } + } + Err(_) => log_in!( + crate::util::logger::PrintType::General, + "SQL error loading users for IOTA" + ), + } + let response = CommunicationValue::new(CommunicationType::IotaUserData) + .with_id(value.get_id()) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); + let _ = connection.send(&response).await; + Ok(()) +} + +pub async fn iota_disconnected( + _: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "IOTA disconnected"); + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .map(|id| id as i64) + else { + return Ok(()); + }; + if user_online_tracker::untrack_iota_connection(iota_id, omikron_id) { + if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { + user_online_tracker::untrack_many_users( + &users.iter().map(|user| user.id.0).collect::>(), + ); + } + } + Ok(()) +} + +pub async fn sync_status( + _: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + if let DataValue::Array(ids) = value.get_data(DataType::UserIds) { + for id in ids { + if let DataValue::SignedNumber(id) = id { + user_online_tracker::track_user_status( + *id as i64, + UserStatus::user_offline, + omikron_id, + ); + } + } + } + if let DataValue::Array(ids) = value.get_data(DataType::IotaIds) { + for id in ids { + if let DataValue::SignedNumber(id) = id { + user_online_tracker::track_iota_connection(*id as i64, omikron_id, true); + } + } + } + Ok(()) +} diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs new file mode 100644 index 0000000..23bf3e7 --- /dev/null +++ b/src/transport/handlers/register.rs @@ -0,0 +1,151 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::{iota_repo, user_repo}, + models::{IotaId, UserId}, +}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::PublicKeyBundle, +}; +use std::sync::Arc; + +pub async fn get_register( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let register_id = user_repo::get_register_id().await?; + let response = CommunicationValue::new(CommunicationType::GetRegister) + .with_id(value.get_id()) + .add_typed_default( + DataType::UserId, + DataValue::SignedNumber(register_id.0.into()), + ); + connection.send(&response).await +} + +pub async fn complete_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let iota_id = value + .get_data(DataType::IotaId) + .as_number() + .map(|id| id as i64); + let public_key = value + .get_data(DataType::PublicKey) + .as_str() + .and_then(|key| PublicKeyBundle::from_base64(key).ok()); + let Some(public_key) = public_key else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + match iota_id { + Some(iota_id) => { + match iota_repo::register_complete_iota(IotaId::from(iota_id), public_key).await { + Ok(()) => { + connection + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.get_id()), + ) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default( + DataType::ErrorType, + DataValue::Str(error.to_string()), + ), + ) + .await + } + } + } + None => match iota_repo::create_new_iota(public_key).await { + Ok(id) => { + connection + .send( + &CommunicationValue::new(CommunicationType::CompleteRegisterIota) + .with_id(value.get_id()) + .add_typed_default( + DataType::IotaId, + DataValue::SignedNumber(id.0.into()), + ), + ) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default( + DataType::ErrorType, + DataValue::Str(error.to_string()), + ), + ) + .await + } + }, + } +} + +pub async fn complete_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let user_id = value + .get_data(DataType::UserId) + .as_number() + .map(|id| id as i64); + let username = value + .get_data(DataType::Username) + .as_str() + .map(str::to_owned); + let public_key = value + .get_data(DataType::PublicKey) + .as_str() + .and_then(|key| PublicKeyBundle::from_base64(key).ok()); + let reset_token = value + .get_data(DataType::ResetToken) + .as_str() + .map(str::to_owned); + let Some((user_id, username, public_key, reset_token)) = user_id + .zip(username) + .zip(public_key) + .zip(reset_token) + .map(|(((id, name), key), token)| (id, name, key, token)) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + match user_repo::register_complete_user( + UserId::from(user_id), + username, + public_key, + IotaId::from(value.get_sender() as i64), + reset_token, + ) + .await + { + Ok(()) => { + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await + } + } +} diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs new file mode 100644 index 0000000..be116b1 --- /dev/null +++ b/src/transport/handlers/states.rs @@ -0,0 +1,46 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::sql::{connection_status::UserStatus, user_online_tracker}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + type_map::TypeMap, +}; +use std::sync::Arc; + +pub async fn get( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let DataValue::Array(ids) = value.get_data(DataType::UserIds) else { + return Ok(()); + }; + let tm = TypeMap::latest(); + let states = ids + .iter() + .filter_map(|id| { + let DataValue::SignedNumber(id) = id else { + return None; + }; + let status = user_online_tracker::get_user_status(*id as i64) + .map(|status| { + if status.connection_type == UserStatus::user_invisible { + UserStatus::user_offline.to_string() + } else { + status.connection_type.to_string() + } + }) + .unwrap_or_else(|| UserStatus::iota_offline.to_string()); + let mut map = Vec::new(); + if let Some(kind) = DataType::UserId.try_to_id(&tm) { + map.push((kind, DataValue::SignedNumber((*id as i64).into()))); + } + if let Some(kind) = DataType::UserState.try_to_id(&tm) { + map.push((kind, DataValue::Str(status))); + } + Some(DataValue::Container(map)) + }) + .collect(); + let response = CommunicationValue::new(CommunicationType::GetStates) + .with_id(value.get_id()) + .add_typed_default(DataType::UserStates, DataValue::Array(states)); + connection.send(&response).await +} diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs new file mode 100644 index 0000000..27c9dbb --- /dev/null +++ b/src/transport/handlers/user_data.rs @@ -0,0 +1,271 @@ +use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use crate::{ + db::{iota_repo, user_repo}, + models::{IotaId, UserId}, + sql::{connection_status::UserStatus, user_online_tracker}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::PublicKeyBundle, +}; +use std::sync::Arc; + +fn connections(iota_id: i64) -> DataValue { + DataValue::Array( + user_online_tracker::get_iota_omikron_connections(iota_id) + .unwrap_or_default() + .into_iter() + .map(|id| DataValue::SignedNumber(id.into())) + .collect(), + ) +} + +pub async fn get_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let user = if let Some(id) = value.get_data(DataType::UserId).as_number() { + user_repo::get_by_user_id(UserId::from(id as i64)) + .await + .ok() + } else if let Some(name) = value.get_data(DataType::Username).as_str() { + user_repo::get_by_username(name).await.ok() + } else { + None + }; + let Some(user) = user else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; + let id = user.id.0; + let iota_id = user.iota_id.0; + let username = user.username.clone(); + let display = user + .display + .filter(|name| !name.is_empty()) + .unwrap_or_else(|| username.clone()); + let mut response = CommunicationValue::new(CommunicationType::GetUserData) + .with_id(value.get_id()) + .add_typed_default(DataType::Username, DataValue::Str(username)) + .add_typed_default( + DataType::PublicKey, + DataValue::Str(user.public_key.to_base64()), + ) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::Display, DataValue::Str(display)) + .add_typed_default( + DataType::SubLevel, + DataValue::SignedNumber(user.sub_level as i128), + ) + .add_typed_default( + DataType::SubEnd, + DataValue::SignedNumber(user.sub_end.into()), + ); + if let Some(status) = user.status.filter(|value| !value.is_empty()) { + response = response.add_typed_default(DataType::Status, DataValue::Str(status)); + } + if let Some(about) = user.about.filter(|value| !value.is_empty()) { + response = response.add_typed_default(DataType::About, DataValue::Str(about)); + } + if let Some(avatar) = user.avatar { + response = + response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar))); + } + let online = user_online_tracker::get_user_status(id); + response = response + .add_typed_default( + DataType::OnlineStatus, + DataValue::Str( + online + .as_ref() + .map(|status| { + if status.connection_type == UserStatus::user_invisible { + UserStatus::user_offline.to_string() + } else { + status.connection_type.to_string() + } + }) + .unwrap_or_else(|| UserStatus::iota_offline.to_string()), + ), + ) + .add_typed_default(DataType::OmikronConnections, connections(iota_id)); + if let Some(status) = online { + response = response.add_typed_default( + DataType::OmikronId, + DataValue::SignedNumber(status.omikron_id.into()), + ); + } + connection.send(&response).await +} + +pub async fn get_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let found = if let Some(id) = value.get_data(DataType::IotaId).as_number() { + iota_repo::get_iota_by_id(IotaId::from(id as i64)) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, None, None)) + } else if let Some(id) = value.get_data(DataType::UserId).as_number() { + if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await { + iota_repo::get_iota_by_id(user.iota_id) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)) + } else { + None + } + } else if let Some(name) = value.get_data(DataType::Username).as_str() { + if let Ok(user) = user_repo::get_by_username(name).await { + iota_repo::get_iota_by_id(user.iota_id) + .await + .ok() + .map(|iota| { + ( + iota.id.0, + iota.public_key, + Some(user.id.0), + Some(name.to_owned()), + ) + }) + } else { + None + } + } else { + None + }; + let Some((id, key, user_id, username)) = found else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; + let mut response = CommunicationValue::new(CommunicationType::GetIotaData) + .with_id(value.get_id()) + .add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())) + .add_typed_default(DataType::OmikronConnections, connections(id)); + if let Some(user_id) = user_id { + response = + response.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())); + } + if let Some(username) = username { + response = response.add_typed_default(DataType::Username, DataValue::Str(username)); + } + connection.send(&response).await +} + +async fn update_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let id = UserId::from(value.get_sender() as i64); + let mut error = None; + if let Some(name) = value.get_data(DataType::Username).as_str() { + error = user_repo::change_username(id, name.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + if error.is_none() { + if let Some(name) = value.get_data(DataType::Display).as_str() { + error = user_repo::change_display_name(id, name.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(avatar) = value.get_data(DataType::Avatar).as_str() { + error = user_repo::change_avatar(id, avatar.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(about) = value.get_data(DataType::About).as_str() { + error = user_repo::change_about(id, about.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(status) = value.get_data(DataType::Status).as_str() { + error = user_repo::change_status(id, status.to_owned()) + .await + .err() + .map(|e| e.to_string()); + } + } + if error.is_none() { + if let Some(key) = value + .get_data(DataType::PublicKey) + .as_str() + .and_then(|key| PublicKeyBundle::from_base64(key).ok()) + { + error = user_repo::change_keys(id, key) + .await + .err() + .map(|e| e.to_string()); + } + } + let response = match error { + None => CommunicationValue::new(CommunicationType::Success), + Some(error) => CommunicationValue::new(CommunicationType::ErrorInternal) + .add_typed_default(DataType::ErrorType, DataValue::Str(error)), + }; + connection.send(&response.with_id(value.get_id())).await +} + +pub async fn change_user( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + update_user(connection, value).await +} + +pub async fn change_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(reset) = value.get_data(DataType::ResetToken).as_str() else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(new_token) = value.get_data(DataType::NewToken).as_str() else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let user_id = UserId::from(value.get_sender() as i64); + let user = match user_repo::get_by_user_id(user_id).await { + Ok(user) => user, + Err(_) => { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + } + }; + if user.token != reset { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + } + let result = + match user_repo::change_iota_id(user_id, IotaId::from(value.get_sender() as i64)).await { + Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, + Err(error) => Err(error), + }; + let response = match result { + Ok(()) => CommunicationValue::new(CommunicationType::Success), + Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + }; + connection.send(&response.with_id(value.get_id())).await +} diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 4f3953a..8171bf2 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,2 +1,4 @@ +pub mod connection; +pub mod handlers; pub mod omikron_connection; pub mod omikron_manager; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 6a5517e..b2b01b3 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,23 +1,22 @@ +use crate::models::OmikronId; use crate::{ - load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, - server::{self, short_link::add_short_link}, - sql::{ - connection_status::UserStatus, - sql::{self, get_by_user_id, get_by_username, get_iota_by_id}, - user_online_tracker::{self}, - }, + load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server, transport::omikron_manager, util::{file_util::load_file_vec, logger::PrintType}, }; -use base64::{Engine as _, engine::general_purpose::STANDARD}; use dashmap::DashMap; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use mtp::host::{AuthenticationPolicy, HostConfig, Policy, SendMode}; -use mtp::webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}; -use mtp::crypto::PublicKeyBundle; -use std::net::{IpAddr, Ipv4Addr}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::PublicKeyBundle, + host::{AuthenticationPolicy, HostConfig, Policy, SendMode}, + webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}, +}; use std::{ - sync::Arc, + net::{IpAddr, Ipv4Addr}, + sync::{ + Arc, + atomic::{AtomicUsize, Ordering}, + }, time::{Duration, Instant}, }; use tokio::{ @@ -27,25 +26,24 @@ use tokio::{ const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); +static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0); +static ACTIVE_CONNECTIONS_BY_IP: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(DashMap::new); -#[derive(Debug, thiserror::Error)] -pub enum OmikronError { - #[error("Not connected")] - NotConnected, - #[error("Not authenticated")] - NotAuthenticated, - #[error("Invalid response")] - InvalidResponse, - #[error("Authentication failed")] - AuthenticationFailed, - #[error("SQL error: {0}")] - Sql(String), - #[error("Send error: {0}")] - Send(String), +struct ConnectionLimitGuard(Option); +impl Drop for ConnectionLimitGuard { + fn drop(&mut self) { + ACTIVE_CONNECTIONS.fetch_sub(1, Ordering::AcqRel); + if let Some(ip) = self.0 { + if let Some(mut count) = ACTIVE_CONNECTIONS_BY_IP.get_mut(&ip) { + *count = count.saturating_sub(1); + } + ACTIVE_CONNECTIONS_BY_IP.remove_if(&ip, |_, count| *count == 0); + } + } } -pub type OmikronResult = Result; - +pub type OmikronResult = crate::error::Result; pub struct WaitingTask { pub task: Box, CommunicationValue) -> bool + Send + Sync>, pub inserted_at: Instant, @@ -58,7 +56,6 @@ pub struct OmikronConnection { waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, } - impl Drop for OmikronConnection { fn drop(&mut self) { if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { @@ -69,15 +66,13 @@ impl Drop for OmikronConnection { impl OmikronConnection { pub fn new(sender: WebMtpSender, id: u64) -> Arc { - let conn = Arc::new(Self { + Arc::new(Self { id, sender: Mutex::new(Some(sender)), ping: RwLock::new(-1), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), - }); - - conn + }) } pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { @@ -86,28 +81,24 @@ impl OmikronConnection { PrintType::Omega, "Omikron connection started" ); - let cleanup_conn = self.clone(); - let cleanup_handle = tokio::spawn(async move { + *self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); loop { ticker.tick().await; cleanup_conn .waiting_tasks - .retain(|_, v| v.inserted_at.elapsed() < MAX_WAITING_AGE); + .retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE); } - }); - *self.cleanup_handle.lock().unwrap() = Some(cleanup_handle); - - while let Ok(cv) = receiver.receive().await { - if let Err(e) = self.clone().process_message(cv).await { - log_err!(0, PrintType::Omega, "Error processing message: {}", e); - if matches!(e, OmikronError::NotConnected) { + })); + while let Ok(value) = receiver.receive().await { + if let Err(error) = self.clone().process_message(value).await { + log_err!(0, PrintType::Omega, "Error processing message: {}", error); + if matches!(error, crate::error::OmegaError::NotConnected) { break; } } } - self.clone().cleanup().await; log_in!( self.id as i64, @@ -116,845 +107,120 @@ impl OmikronConnection { ); } - async fn process_message(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { - log_cv_in!(PrintType::Omikron, &cv); + async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { + if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { + log_cv_in!(PrintType::Omikron, &value); } - - let msg_id = cv.get_id(); - - if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { - let _ = (task.task)(self.clone(), cv); + if let Some((_, task)) = self.waiting_tasks.remove(&value.get_id()) { + let _ = (task.task)(self.clone(), value); return Ok(()); } - - if cv.is_type(CommunicationType::Ping) { - return self.handle_ping(cv).await; + if value.is_type(CommunicationType::Ping) { + return self.ping(value).await; } - - let omikron_id = self.id as i64; - self.clone().handle_authenticated(cv, omikron_id).await + self.dispatch(value).await } - async fn handle_authenticated( - self: Arc, - cv: CommunicationValue, - omikron_id: i64, - ) -> OmikronResult<()> { - let comm_type = cv.get_comm_type_enum(); - match comm_type { - Some(CommunicationType::ShortenLink) => self.handle_shorten_link(cv).await, - + async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { + let id = self.id as i64; + match value.get_comm_type_enum() { + Some(CommunicationType::ShortenLink) => { + crate::transport::handlers::links::shorten(self, value).await + } Some(CommunicationType::UserConnected) => { - self.handle_user_connected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::user_connected(self, value, id).await } Some(CommunicationType::UserDisconnected) => { - self.handle_user_disconnected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::user_disconnected(self, value, id).await } Some(CommunicationType::IotaConnected) => { - self.handle_iota_connected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::iota_connected(self, value, id).await } Some(CommunicationType::IotaDisconnected) => { - self.handle_iota_disconnected(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::iota_disconnected(self, value, id).await } Some(CommunicationType::SyncClientIotaStatus) => { - self.handle_sync_status(cv, omikron_id).await; - Ok(()) + crate::transport::handlers::presence::sync_status(self, value, id).await + } + Some(CommunicationType::GetUserData) => { + crate::transport::handlers::user_data::get_user(self, value).await + } + Some(CommunicationType::GetIotaData) => { + crate::transport::handlers::user_data::get_iota(self, value).await + } + Some(CommunicationType::ChangeUserData) => { + crate::transport::handlers::user_data::change_user(self, value).await + } + Some(CommunicationType::ChangeIotaData) => { + crate::transport::handlers::user_data::change_iota(self, value).await + } + Some(CommunicationType::GetRegister) => { + crate::transport::handlers::register::get_register(self, value).await } - - Some(CommunicationType::GetUserData) => self.handle_get_user_data(cv).await, - Some(CommunicationType::GetIotaData) => self.handle_get_iota_data(cv).await, - - Some(CommunicationType::GetRegister) => self.handle_get_register(cv).await, Some(CommunicationType::CompleteRegisterIota) => { - self.handle_complete_register_iota(cv).await + crate::transport::handlers::register::complete_iota(self, value).await } Some(CommunicationType::CompleteRegisterUser) => { - self.handle_complete_register_user(cv).await + crate::transport::handlers::register::complete_user(self, value).await + } + Some(CommunicationType::DeleteUser) => { + crate::transport::handlers::account::user(self, value).await + } + Some(CommunicationType::DeleteIota) => { + crate::transport::handlers::account::iota(self, value).await + } + Some(CommunicationType::GetNotifications) => { + crate::transport::handlers::notifications::get(self, value).await + } + Some(CommunicationType::ReadNotification) => { + crate::transport::handlers::notifications::read(self, value).await + } + Some(CommunicationType::PushNotification) => { + crate::transport::handlers::notifications::push(self, value).await + } + Some(CommunicationType::GetStates) => { + crate::transport::handlers::states::get(self, value).await } - - Some(CommunicationType::ChangeUserData) => self.handle_change_user_data(cv).await, - Some(CommunicationType::ChangeIotaData) => self.handle_change_iota_data(cv).await, - Some(CommunicationType::DeleteUser) => self.handle_delete_user(cv).await, - Some(CommunicationType::DeleteIota) => self.handle_delete_iota(cv).await, - - Some(CommunicationType::GetNotifications) => self.handle_get_notifications(cv).await, - Some(CommunicationType::ReadNotification) => self.handle_read_notification(cv).await, - Some(CommunicationType::PushNotification) => self.handle_push_notification(cv).await, - Some(CommunicationType::GetStates) => self.handle_get_states(cv).await, - _ => { log_err!( 0, PrintType::Omega, "Unknown message type: {:?}", - cv.get_type() + value.get_type() ); Ok(()) } } } - async fn handle_shorten_link(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let link = cv - .get_data(DataType::Link) - .as_str() - .ok_or(OmikronError::InvalidResponse)?; - - let short = add_short_link(link) - .await - .map_err(|_| OmikronError::Sql("Shortend link Error".to_string()))?; - - let response = CommunicationValue::new(CommunicationType::ShortenLink) - .with_id(cv.get_id()) - .add_typed_default(DataType::Link, DataValue::Str(short)); - - self.send(&response).await - } - - async fn handle_user_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "User connected"); - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - let status = cv - .get_data(DataType::UserState) - .as_str() - .and_then(|s| UserStatus::from_str(s)) - .unwrap_or(UserStatus::user_online); - user_online_tracker::track_user_status(user_id.try_into().unwrap(), status, omikron_id); - } - } - - async fn handle_user_disconnected(self: Arc, cv: CommunicationValue, _omikron_id: i64) { - log_in!(PrintType::Omega, "User disconnected"); - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - if let Some(status) = user_online_tracker::get_user_status(user_id as i64) { - user_online_tracker::track_user_status( - user_id as i64, - UserStatus::user_offline, - status.omikron_id, - ); - } - } - } - - async fn handle_iota_connected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA connected"); - let iota_id = match cv.get_data(DataType::IotaId).as_number() { - Some(id) => id as i64, - None => return, - }; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - - let mut user_ids = Vec::new(); - if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { - for (user_id, _, _, _, _, _, _, _, _, _, _, _) in users { - user_ids.push(DataValue::SignedNumber(user_id.try_into().unwrap())); - user_online_tracker::track_user_status( - user_id.try_into().unwrap(), - UserStatus::user_offline, - omikron_id, - ); - } - } else { - log_in!(PrintType::General, "SQL error loading users for IOTA"); - } - - let response = CommunicationValue::new(CommunicationType::IotaUserData) - .with_id(cv.get_id()) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); - - let _ = self.send(&response).await; - } - - async fn handle_iota_disconnected(self: Arc, cv: CommunicationValue, omikron_id: i64) { - log_in!(PrintType::Omega, "IOTA disconnected"); - let iota_id = match cv.get_data(DataType::IotaId).as_number() { - Some(id) => id as i64, - None => return, - }; - let iota_offline = user_online_tracker::untrack_iota_connection(iota_id, omikron_id); - if iota_offline { - if let Ok(users) = sql::get_users_by_iota_id(iota_id.try_into().unwrap()).await { - let user_ids: Vec = users.iter().map(|u| u.0.try_into().unwrap()).collect(); - user_online_tracker::untrack_many_users(&user_ids); - } - } - } - - async fn handle_sync_status(self: Arc, cv: CommunicationValue, omikron_id: i64) { - if let DataValue::Array(user_ids) = cv.get_data(DataType::UserIds) { - for user_id_val in user_ids { - if let DataValue::SignedNumber(user_id) = user_id_val { - user_online_tracker::track_user_status( - *user_id as i64, - UserStatus::user_offline, - omikron_id, - ); - } - } - } - - if let DataValue::Array(iota_ids) = cv.get_data(DataType::IotaIds) { - for iota_id_val in iota_ids { - if let DataValue::SignedNumber(iota_id) = iota_id_val { - user_online_tracker::track_iota_connection(*iota_id as i64, omikron_id, true); - } - } - } - } - - async fn handle_get_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - if let Ok(user_data) = get_by_user_id(user_id as i64).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - if let Some(username) = cv.get_data(DataType::Username).as_str() { - if let Ok(user_data) = get_by_username(username).await { - let response = self - .clone() - .build_user_data_response(cv.get_id(), user_data) - .await; - return self.send(&response).await; - } - } - - let response = - CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_user_data_response( - self: Arc, - msg_id: u32, - user: ( - i64, - i64, - String, - Option, - Option, - Option, - Option>, - i32, - i64, - PublicKeyBundle, - String, - String, - ), - ) -> CommunicationValue { - let ( - id, - iota_id, - username, - display, - status, - about, - avatar, - sub_level, - sub_end, - public_key, - _, - _, - ) = user; - - let mut response = CommunicationValue::new(CommunicationType::GetUserData) - .with_id(msg_id) - .add_typed_default(DataType::Username, DataValue::Str(username.clone())) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) - .add_typed_default( - DataType::SubLevel, - DataValue::SignedNumber(sub_level as i128), - ) - .add_typed_default(DataType::SubEnd, DataValue::SignedNumber(sub_end.into())); - - let display_name = display.filter(|d| !d.is_empty()).unwrap_or(username); - response = response.add_typed_default(DataType::Display, DataValue::Str(display_name)); - - if let Some(s) = status.filter(|s| !s.is_empty()) { - response = response.add_typed_default(DataType::Status, DataValue::Str(s)); - } - if let Some(a) = about.filter(|a| !a.is_empty()) { - response = response.add_typed_default(DataType::About, DataValue::Str(a)); - } - if let Some(av) = avatar { - response = - response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(av))); - } - - let user_status = user_online_tracker::get_user_status(id); - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - if let Some(us) = user_status { - let display_status = if us.connection_type == UserStatus::user_invisible { - UserStatus::user_offline - } else { - us.connection_type.clone() - }; - response = response.add_typed_default( - DataType::OnlineStatus, - DataValue::Str(display_status.to_string()), - ); - response = response.add_typed_default( - DataType::OmikronId, - DataValue::SignedNumber(us.omikron_id.into()), - ); - } else { - response = response.add_typed_default( - DataType::OnlineStatus, - DataValue::Str(UserStatus::iota_offline.to_string()), - ); - } - - response = response.add_typed_default( - DataType::OmikronConnections, - DataValue::Array( - iota_connections - .into_iter() - .map(|id| DataValue::SignedNumber(id as i128)) - .collect(), - ), - ); - - response - } - - async fn handle_get_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id as i64).await { - let response = self - .clone() - .build_iota_data_response(cv.get_id(), iota_id, public_key, None, None) - .await; - return self.send(&response).await; - } - } - - if let Some(user_id) = cv.get_data(DataType::UserId).as_number() { - if let Ok((_, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_user_id(user_id as i64).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id as i64), - None, - ) - .await; - return self.send(&response).await; - } - } - } - - if let Some(username) = cv.get_data(DataType::Username).as_str() { - if let Ok((user_id, iota_id, _, _, _, _, _, _, _, _, _, _)) = - get_by_username(username).await - { - if let Ok((iota_id, public_key)) = get_iota_by_id(iota_id).await { - let response = self - .clone() - .build_iota_data_response( - cv.get_id(), - iota_id, - public_key, - Some(user_id), - Some(username.to_string()), - ) - .await; - return self.send(&response).await; - } - } - } - - let response = - CommunicationValue::new(CommunicationType::ErrorNotFound).with_id(cv.get_id()); - self.send(&response).await - } - - async fn build_iota_data_response( - self: Arc, - msg_id: u32, - iota_id: i64, - public_key: PublicKeyBundle, - user_id: Option, - username: Option, - ) -> CommunicationValue { - let mut response = CommunicationValue::new(CommunicationType::GetIotaData) - .with_id(msg_id) - .add_typed_default(DataType::PublicKey, DataValue::Str(public_key.to_base64())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); - - if let Some(uid) = user_id { - response = - response.add_typed_default(DataType::UserId, DataValue::SignedNumber(uid.into())); - } - if let Some(uname) = username { - response = response.add_typed_default(DataType::Username, DataValue::Str(uname)); - } - - let iota_connections = - user_online_tracker::get_iota_omikron_connections(iota_id).unwrap_or_default(); - - response.add_typed_default( - DataType::OmikronConnections, - DataValue::Array( - iota_connections - .into_iter() - .map(|id| DataValue::SignedNumber(id as i128)) - .collect(), - ), - ) - } - - async fn handle_get_register(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let register_id = sql::get_register_id().await; - let response = CommunicationValue::new(CommunicationType::GetRegister) - .with_id(cv.get_id()) - .add_typed_default( - DataType::UserId, - DataValue::SignedNumber(register_id as i128), - ); - self.send(&response).await - } - - async fn handle_complete_register_iota( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let iota_id_opt = cv.get_data(DataType::IotaId).as_number().map(|n| n as i64); - - let public_key = cv - .get_data(DataType::PublicKey) - .as_str() - .and_then(|s| PublicKeyBundle::from_base64(s).ok()); - - if let Some(public_key) = public_key { - if let Some(iota_id) = iota_id_opt { - match sql::register_complete_iota(iota_id, public_key).await { - Ok(_) => { - let response = CommunicationValue::new(CommunicationType::Success) - .with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - match sql::create_new_iota(public_key).await { - Ok(new_iota_id) => { - let response = - CommunicationValue::new(CommunicationType::CompleteRegisterIota) - .with_id(cv.get_id()) - .add_typed_default( - DataType::IotaId, - DataValue::SignedNumber(new_iota_id.into()), - ); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) - .await - } - } - - async fn handle_complete_register_user( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_data(DataType::UserId).as_number().map(|n| n as i64); - let username = cv - .get_data(DataType::Username) - .as_str() - .map(|s| s.to_string()); - let public_key = cv - .get_data(DataType::PublicKey) - .as_str() - .and_then(|s| PublicKeyBundle::from_base64(s).ok()); - let iota_id = cv.get_sender(); - let reset_token = cv - .get_data(DataType::ResetToken) - .as_str() - .map(|s| s.to_string()); - - if let (Some(uid), Some(uname), Some(pk), Some(rt)) = - (user_id, username, public_key, reset_token) - { - match sql::register_complete_user(uid, uname, pk, iota_id as i64, rt).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) - .await - } - } - - async fn handle_change_user_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - let mut success = true; - let mut error_message = String::new(); - - if let Some(username) = cv.get_data(DataType::Username).as_str() { - if let Err(e) = sql::change_username(user_id, username.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(display) = cv.get_data(DataType::Display).as_str() { - if let Err(e) = sql::change_display_name(user_id, display.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(avatar) = cv.get_data(DataType::Avatar).as_str() { - if let Err(e) = sql::change_avatar(user_id, avatar.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(about) = cv.get_data(DataType::About).as_str() { - if let Err(e) = sql::change_about(user_id, about.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let Some(status) = cv.get_data(DataType::Status).as_str() { - if let Err(e) = sql::change_status(user_id, status.to_string()).await { - success = false; - error_message = e.to_string(); - } - } - if let (Some(public_key), Some(private_key_hash)) = ( - cv.get_data(DataType::PublicKey) - .as_str() - .and_then(|s| PublicKeyBundle::from_base64(s).ok()), - cv.get_data(DataType::PrivateKeyHash).as_str(), - ) { - if let Err(e) = - sql::change_keys(user_id, public_key, private_key_hash.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } else { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(error_message)); - self.send(&response).await - } - } - - async fn handle_change_iota_data(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - - if let (iota_id, Some(reset_token), Some(new_token)) = ( - cv.get_sender(), - cv.get_data(DataType::ResetToken).as_str(), - cv.get_data(DataType::NewToken).as_str(), - ) { - match sql::get_by_user_id(user_id).await { - Ok(user) => { - let current_token = user.11; - if current_token == reset_token { - let mut success = true; - let mut error_message = String::new(); - - if let Err(e) = sql::change_iota_id(user_id, iota_id as i64).await { - success = false; - error_message = e.to_string(); - } - if success { - if let Err(e) = sql::change_token(user_id, new_token.to_string()).await - { - success = false; - error_message = e.to_string(); - } - } - - if success { - let response = CommunicationValue::new(CommunicationType::Success) - .with_id(cv.get_id()); - self.send(&response).await - } else { - let response = - CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error_message), - ); - self.send(&response).await - } - } else { - self.send_error_response( - cv.get_id(), - CommunicationType::ErrorInvalidChallenge, - ) - .await - } - } - Err(_) => { - self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound) - .await - } - } - } else { - self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidData) - .await - } - } - - async fn handle_delete_user(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - match sql::delete_user(user_id).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - - async fn handle_delete_iota(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let iota_id = cv.get_sender(); - match sql::delete_iota(iota_id as i64).await { - Ok(_) => { - let response = - CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); - self.send(&response).await - } - Err(e) => { - let response = CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(cv.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(e.to_string())); - self.send(&response).await - } - } - } - - async fn handle_get_notifications( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let user_id = cv.get_sender() as i64; - let response_array = match sql::get_notifications(user_id).await { - Ok(notifications) => notifications - .into_iter() - .map(|(sender, amount)| { - let tm = mtp::type_map::TypeMap::latest(); - DataValue::Container(vec![ - ( - DataType::SenderId.to_id(&tm), - DataValue::SignedNumber(sender.into()), - ), - ( - DataType::Amount.to_id(&tm), - DataValue::SignedNumber(amount.into()), - ), - ]) - }) - .collect(), - Err(e) => { - log!(PrintType::General, "SQL get_notifications error: {}", e); - vec![] - } - }; - - let response = CommunicationValue::new(CommunicationType::GetNotifications) - .with_id(cv.get_id()) - .add_typed_default(DataType::Notifications, DataValue::Array(response_array)); - self.send(&response).await - } - - async fn handle_read_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let receiver_id = match cv.get_sender() { - s if s > 0 => s as i64, - _ => match cv.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; - - if let Some(other_id) = cv - .get_data(DataType::SenderId) - .as_number() - .map(|n| n as i64) - { - if let Err(e) = sql::read_notification(receiver_id, other_id).await { - log!(PrintType::General, "SQL read_notification error: {}", e); - } else { - let response = CommunicationValue::new(CommunicationType::ReadNotification) - .with_id(cv.get_id()); - let _ = self.send(&response).await; - - let sync_cv = CommunicationValue::new(CommunicationType::ReadNotification) - .with_receiver(receiver_id as u64) - .add_typed_default( - DataType::SenderId, - DataValue::SignedNumber(other_id.into()), - ); - crate::transport::omikron_manager::send_to_user(receiver_id, &sync_cv).await; - } - } - Ok(()) - } - - async fn handle_push_notification( - self: Arc, - cv: CommunicationValue, - ) -> OmikronResult<()> { - let receiver_id = match cv.get_receiver() { - r if r > 0 => r as i64, - _ => match cv.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; - - let sender_id = match cv.get_data(DataType::SenderId).as_number() { - Some(id) => id as i64, - None => cv.get_sender() as i64, - }; - - if let Err(e) = sql::add_notification(receiver_id, sender_id).await { - log!(PrintType::General, "SQL add_notification error: {}", e); - } else { - let response = - CommunicationValue::new(CommunicationType::PushNotification).with_id(cv.get_id()); - let _ = self.send(&response).await; - - let push_cv = CommunicationValue::new(CommunicationType::PushNotification) - .with_receiver(receiver_id as u64) - .add_typed_default( - DataType::SenderId, - DataValue::SignedNumber(sender_id.into()), - ); - crate::transport::omikron_manager::send_to_user(receiver_id, &push_cv).await; - } - Ok(()) - } - - async fn handle_get_states(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - let user_ids = match cv.get_data(DataType::UserIds) { - DataValue::Array(ids) => ids, - _ => return Ok(()), - }; - - let mut states = Vec::new(); - for id_val in user_ids { - if let DataValue::SignedNumber(user_id) = id_val { - let user_id = *user_id as i64; - let status = user_online_tracker::get_user_status(user_id); - let status_str = match status { - Some(ref us) => { - if us.connection_type == UserStatus::user_invisible { - "user_offline".to_string() - } else { - us.connection_type.to_string() - } - } - None => UserStatus::iota_offline.to_string(), - }; - let tm = mtp::type_map::TypeMap::latest(); - let mut map = Vec::new(); - map.push(( - DataType::UserId.to_id(&tm), - DataValue::SignedNumber(user_id.into()), - )); - map.push((DataType::UserState.to_id(&tm), DataValue::Str(status_str))); - states.push(DataValue::Container(map)); - } - } - - let response = CommunicationValue::new(CommunicationType::GetStates) - .with_id(cv.get_id()) - .add_typed_default(DataType::UserStates, DataValue::Array(states)); - self.send(&response).await - } - - async fn handle_ping(self: Arc, cv: CommunicationValue) -> OmikronResult<()> { - if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { + async fn ping(self: Arc, value: CommunicationValue) -> OmikronResult<()> { + if let DataValue::SignedNumber(last_ping) = value.get_data(DataType::LastPing) { *self.ping.write().await = *last_ping as i64; } - - let response = CommunicationValue::new(CommunicationType::Pong).with_id(cv.get_id()); + let response = CommunicationValue::new(CommunicationType::Pong).with_id(value.get_id()); self.send(&response).await } - - async fn send(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - if !cv.is_type(CommunicationType::Pong) && !cv.is_type(CommunicationType::Ping) { - log_cv_out!(PrintType::Omikron, cv); + pub(crate) async fn send(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { + if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { + log_cv_out!(PrintType::Omikron, value); } - let guard = self.sender.lock().await; - let sender = guard.as_ref().ok_or(OmikronError::NotConnected)?; - + let sender = guard + .as_ref() + .ok_or(crate::error::OmegaError::NotConnected)?; sender - .send(cv) + .send(value) .await - .map_err(|e| OmikronError::Send(e.to_string())) + .map_err(|error| crate::error::OmegaError::SendError(error.to_string())) } - - async fn send_error_response( + pub(crate) async fn send_error_response( self: Arc, message_id: u32, error_type: CommunicationType, ) -> OmikronResult<()> { - let error = CommunicationValue::new(error_type).with_id(message_id); - self.send(&error).await + self.send(&CommunicationValue::new(error_type).with_id(message_id)) + .await } - pub async fn close(self: Arc) { log_in!( self.get_omikron_id().await.unwrap_or(0), @@ -962,51 +228,38 @@ impl OmikronConnection { "Omikron connection Closed" ); } - async fn cleanup(self: Arc) { if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); omikron_manager::remove_omikron(self.id as i64).await; - user_online_tracker::untrack_omikron(self.id as i64).await; + crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; } - if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); } } - pub async fn get_omikron_id(self: Arc) -> Option { Some(self.id as i64) } - - pub async fn send_message(self: Arc, cv: &CommunicationValue) -> OmikronResult<()> { - self.send(cv).await + pub async fn send_message(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { + self.send(value).await } } -// ============================================================================ -// Server Startup -// ============================================================================ - -pub async fn get_by_omikron_id( - omikron_id: u64, - _description: Option, -) -> Option { - sql::get_omikron_by_id(omikron_id as i64) +pub async fn get_by_omikron_id(omikron_id: u64, _: Option) -> Option { + crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64)) .await .ok() - .map(|(bundle, _ip_address, _port)| bundle) + .map(|omikron| omikron.public_key) } -pub async fn complete_register(_pub_key: PublicKeyBundle, _description: Option) -> u64 { +pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { 0 } pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem").expect("Error loading Pemfile"); - let key_pem = load_file_vec("certs", "transport_key.pem").expect("Error loading Keyfile"); - + let cert_pem = load_file_vec("certs", "transport_cert.pem")?; + let key_pem = load_file_vec("certs", "transport_key.pem")?; let web_config = server::server::build_web_config()?; - let host_config = HostConfig::new( IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), port, @@ -1034,30 +287,51 @@ pub async fn start(port: u16) -> Result<(), Box> { }) .with_authentication( load_keyring(), - Box::new(|user_id, description| Box::pin(get_by_omikron_id(user_id, description))), - Box::new(|pub_key, description| Box::pin(complete_register(pub_key, description))), + Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))), + Box::new(|key, description| Box::pin(complete_register(key, description))), ) .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); - let mut server = MTPWebServer::new(host_config, web_config).await?; log!("OmegaServer listening on port {}", port); - loop { let mut conn = match server.accept().await { Ok(Some(conn)) => conn, Ok(None) => break, - Err(e) => { - log_err!(0, PrintType::Omega, "Rejected omikron connection: {}", e); + Err(error) => { + log_err!( + 0, + PrintType::Omega, + "Rejected omikron connection: {}", + error + ); continue; } }; - - let omikron_connection = OmikronConnection::new(conn.sender, conn.client_id); + let config = crate::config::RateLimitConfig::from_env(); + let peer_ip = conn.remote_addr.map(|address| address.ip()); + let active = ACTIVE_CONNECTIONS.fetch_add(1, Ordering::AcqRel) + 1; + let peer_active = peer_ip.map(|ip| { + let mut count = ACTIVE_CONNECTIONS_BY_IP.entry(ip).or_insert(0); + *count += 1; + *count + }); + if active > config.transport_connections + || peer_active.is_some_and(|count| count > config.transport_connections_per_ip) + { + drop(ConnectionLimitGuard(peer_ip)); + log_err!( + 0, + PrintType::Omega, + "Rejected connection: active connection limit reached" + ); + continue; + } + let connection = OmikronConnection::new(conn.sender, conn.client_id); tokio::spawn(async move { - omikron_manager::add_omikron(omikron_connection.clone()).await; - omikron_connection.handle(&mut conn.receiver).await; + let _guard = ConnectionLimitGuard(peer_ip); + omikron_manager::add_omikron(connection.clone()).await; + connection.handle(&mut conn.receiver).await; }); } - Ok(()) } diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index f98a75c..2de4ea3 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,4 +1,4 @@ -use crate::transport::omikron_connection::OmikronConnection; +use crate::transport::connection::OmikronConnection; use dashmap::DashMap; use mtp::codec::CommunicationValue; use once_cell::sync::Lazy; @@ -27,11 +27,9 @@ pub async fn remove_omikron(omikron_id: i64) { } pub async fn get_random_omikron() -> Result, ()> { - let mut rng = rand::thread_rng(); - let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); - if let Some(key) = keys.into_iter().choose(&mut rng) { + if let Some(key) = keys.into_iter().choose(&mut rand::rng()) { if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { return Ok(entry.clone()); } diff --git a/src/util/file_util.rs b/src/util/file_util.rs index 593adbe..8630489 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -8,9 +8,8 @@ use zip::ZipArchive; use crate::log; -static WORKING_DIR: Lazy = Lazy::new(|| { - std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")) -}); +static WORKING_DIR: Lazy = + Lazy::new(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); pub fn delete_file(path: &str, name: &str) -> bool { let dir = Path::new(&get_directory()).join(path); diff --git a/src/util/logger.rs b/src/util/logger.rs index 31606b6..39eb933 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -269,8 +269,7 @@ fn format_data_container(data: Vec<(DataTypeId, DataValue)>, version: Version) - DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), DataValue::Container(inner) => { - let inner_formatted = - format_data_container(inner, version.clone()); + let inner_formatted = format_data_container(inner, version.clone()); format!("{}={{ {} }}", key_str, inner_formatted) } From 5d5dabcdb05d73ede3ac20d028d4803fadf71e06 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:27:40 +0200 Subject: [PATCH 185/220] [Fix] flake & .pems --- flake.nix | 28 +++++++--------------------- src/transport/omikron_connection.rs | 4 ++-- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/flake.nix b/flake.nix index 6e40316..1d48d42 100644 --- a/flake.nix +++ b/flake.nix @@ -110,24 +110,14 @@ description = "Directory where Omega stores its data and reads certificates from."; }; - apiCertFile = lib.mkOption { + certFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL certificate file for the web/API server (e.g. ACME fullchain.pem). Copied to server_cert.pem at runtime."; + description = "Path to the SSL certificate file (e.g. ACME fullchain.pem). Copied to cert.pem at runtime."; }; - apiKeyFile = lib.mkOption { + keyFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL private key file for the web/API server (e.g. ACME key.pem). Converted to PKCS#8 and copied to server_key.pem at runtime."; - }; - - transportCertFile = lib.mkOption { - type = lib.types.path; - description = "Path to the SSL certificate file for the MTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; - }; - - transportKeyFile = lib.mkOption { - type = lib.types.path; - description = "Path to the SSL private key file for the MTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; + description = "Path to the SSL private key file (e.g. ACME key.pem). Converted to PKCS#8 and copied to key.pem at runtime."; }; environmentFiles = lib.mkOption { @@ -211,14 +201,10 @@ "+" + pkgs.writeShellScript "omega-setup-certs" '' mkdir -p ${cfg.dataDir}/certs - cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem + cp ${cfg.certFile} ${cfg.dataDir}/certs/cert.pem ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.apiKeyFile} \ - -out ${cfg.dataDir}/certs/server_key.pem - cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem - ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.transportKeyFile} \ - -out ${cfg.dataDir}/certs/transport_key.pem + -in ${cfg.keyFile} \ + -out ${cfg.dataDir}/certs/key.pem chown -R omega:omega ${cfg.dataDir} '' ) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index b2b01b3..7095f9d 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -257,8 +257,8 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { } pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem")?; - let key_pem = load_file_vec("certs", "transport_key.pem")?; + let cert_pem = load_file_vec("certs", "cert.pem")?; + let key_pem = load_file_vec("certs", "key.pem")?; let web_config = server::server::build_web_config()?; let host_config = HostConfig::new( IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), From fa0af21af58ec82bd1f9629808d28b41797cc35d Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Mon, 20 Jul 2026 23:27:40 +0200 Subject: [PATCH 186/220] [Fix] flake & .pems --- flake.nix | 28 +++++++--------------------- src/transport/omikron_connection.rs | 4 ++-- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/flake.nix b/flake.nix index 6e40316..1d48d42 100644 --- a/flake.nix +++ b/flake.nix @@ -110,24 +110,14 @@ description = "Directory where Omega stores its data and reads certificates from."; }; - apiCertFile = lib.mkOption { + certFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL certificate file for the web/API server (e.g. ACME fullchain.pem). Copied to server_cert.pem at runtime."; + description = "Path to the SSL certificate file (e.g. ACME fullchain.pem). Copied to cert.pem at runtime."; }; - apiKeyFile = lib.mkOption { + keyFile = lib.mkOption { type = lib.types.path; - description = "Path to the SSL private key file for the web/API server (e.g. ACME key.pem). Converted to PKCS#8 and copied to server_key.pem at runtime."; - }; - - transportCertFile = lib.mkOption { - type = lib.types.path; - description = "Path to the SSL certificate file for the MTP/QUIC transport (e.g. ACME fullchain.pem). Copied to transport_cert.pem at runtime."; - }; - - transportKeyFile = lib.mkOption { - type = lib.types.path; - description = "Path to the SSL private key file for the MTP/QUIC transport (e.g. ACME key.pem). Converted to PKCS#8 and copied to transport_key.pem at runtime."; + description = "Path to the SSL private key file (e.g. ACME key.pem). Converted to PKCS#8 and copied to key.pem at runtime."; }; environmentFiles = lib.mkOption { @@ -211,14 +201,10 @@ "+" + pkgs.writeShellScript "omega-setup-certs" '' mkdir -p ${cfg.dataDir}/certs - cp ${cfg.apiCertFile} ${cfg.dataDir}/certs/server_cert.pem + cp ${cfg.certFile} ${cfg.dataDir}/certs/cert.pem ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.apiKeyFile} \ - -out ${cfg.dataDir}/certs/server_key.pem - cp ${cfg.transportCertFile} ${cfg.dataDir}/certs/transport_cert.pem - ${pkgs.openssl}/bin/openssl pkcs8 -topk8 -nocrypt \ - -in ${cfg.transportKeyFile} \ - -out ${cfg.dataDir}/certs/transport_key.pem + -in ${cfg.keyFile} \ + -out ${cfg.dataDir}/certs/key.pem chown -R omega:omega ${cfg.dataDir} '' ) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index b2b01b3..7095f9d 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -257,8 +257,8 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { } pub async fn start(port: u16) -> Result<(), Box> { - let cert_pem = load_file_vec("certs", "transport_cert.pem")?; - let key_pem = load_file_vec("certs", "transport_key.pem")?; + let cert_pem = load_file_vec("certs", "cert.pem")?; + let key_pem = load_file_vec("certs", "key.pem")?; let web_config = server::server::build_web_config()?; let host_config = HostConfig::new( IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), From a65f76763aff538999f0fb2c54a1149c249e1579 Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 25 Jul 2026 13:11:07 +0200 Subject: [PATCH 187/220] (chore): update license --- LICENSE | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index d218eff..e952c84 100644 --- a/LICENSE +++ b/LICENSE @@ -1,16 +1,15 @@ -Copyright (c) [2025] [Methanium] +Copyright (c) 2025 Methanium + All rights reserved. -This software is protected by copyright. Copying, editing, -distributing, publicly performing, or any other use of this software -or its components, in source or binary form, is strictly prohibited without the express -written permission of the copyright holder. +No part of this software, source code, documentation, or +associated materials may be copied, reproduced, modified, +distributed, published, sublicensed, sold, or used to create +derivative works without prior written permission from the +copyright holder. -FUTURE LICENSE ACCEPTANCE: -It is the copyright holder's intention to release this software in the future -under a license yet to be defined, which will, among other things, -allow private, non-commercial use. This statement does not constitute -a current license grant and does not alter the above -prohibition on use, copying, or modification. Until the formal -publication of such a future license, all rights remain -reserved. +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY +OF ANY KIND, EXPRESS OR IMPLIED. TO THE MAXIMUM +EXTENT PERMITTED BY LAW, THE COPYRIGHT HOLDER SHALL +NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER +LIABILITY ARISING FROM THE SOFTWARE OR ITS USE. From 04d456bd8afddc99eaecda008e09b7ab31cfb961 Mon Sep 17 00:00:00 2001 From: Alois Date: Sat, 25 Jul 2026 13:11:07 +0200 Subject: [PATCH 188/220] (chore): update license --- LICENSE | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index d218eff..e952c84 100644 --- a/LICENSE +++ b/LICENSE @@ -1,16 +1,15 @@ -Copyright (c) [2025] [Methanium] +Copyright (c) 2025 Methanium + All rights reserved. -This software is protected by copyright. Copying, editing, -distributing, publicly performing, or any other use of this software -or its components, in source or binary form, is strictly prohibited without the express -written permission of the copyright holder. +No part of this software, source code, documentation, or +associated materials may be copied, reproduced, modified, +distributed, published, sublicensed, sold, or used to create +derivative works without prior written permission from the +copyright holder. -FUTURE LICENSE ACCEPTANCE: -It is the copyright holder's intention to release this software in the future -under a license yet to be defined, which will, among other things, -allow private, non-commercial use. This statement does not constitute -a current license grant and does not alter the above -prohibition on use, copying, or modification. Until the formal -publication of such a future license, all rights remain -reserved. +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY +OF ANY KIND, EXPRESS OR IMPLIED. TO THE MAXIMUM +EXTENT PERMITTED BY LAW, THE COPYRIGHT HOLDER SHALL +NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER +LIABILITY ARISING FROM THE SOFTWARE OR ITS USE. From 80820501704eebe6f24bd13898e5135f3df9f640 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 27 Jul 2026 20:36:23 +0200 Subject: [PATCH 189/220] [Fix] Stability --- .cargo/config.toml | 2 +- .gitmodules | 3 + Cargo.lock | 85 +++--- .../{001.sql => 001_initial_schema.sql} | 16 +- migrations/002_registration_leases.sql | 13 + .../003_registration_lease_request_id.sql | 8 + mtp-type-maps | 1 + src/config.rs | 4 +- src/db/iota_repo.rs | 26 +- src/db/user_repo.rs | 182 +++++++++++- src/server/api.rs | 37 ++- src/server/index.rs | 29 +- src/server/server.rs | 7 +- src/sql/user_online_tracker.rs | 56 ++++ src/transport/handlers/presence.rs | 59 +++- src/transport/handlers/register.rs | 119 ++++---- src/transport/omikron_connection.rs | 94 +++--- src/transport/omikron_manager.rs | 10 +- src/util/logger.rs | 7 +- type-maps.yaml | 269 ------------------ 20 files changed, 563 insertions(+), 464 deletions(-) create mode 100644 .gitmodules rename migrations/{001.sql => 001_initial_schema.sql} (91%) create mode 100644 migrations/002_registration_leases.sql create mode 100644 migrations/003_registration_lease_request_id.sql create mode 160000 mtp-type-maps delete mode 100644 type-maps.yaml diff --git a/.cargo/config.toml b/.cargo/config.toml index d363b83..46adaad 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [env] -MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true } +MTP_TYPE_MAPS = { value = "mtp-type-maps/type-maps.yaml", relative = true } diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1e9ee12 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mtp-type-maps"] + path = mtp-type-maps + url = https://git.methanium.net/tensamin/mtp-type-maps diff --git a/Cargo.lock b/Cargo.lock index a20fbae..8c1774a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ "cipher 0.5.2", "cpubits", @@ -91,7 +91,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -217,9 +217,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -320,15 +320,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -641,9 +632,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" dependencies = [ "serde", ] @@ -686,11 +677,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -1124,6 +1114,12 @@ 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 = "hybrid-array" version = "0.4.13" @@ -1148,6 +1144,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -1444,9 +1441,9 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libm" @@ -1617,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-client", "mtp-codec", @@ -1633,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-codec", "mtp-common", @@ -1646,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "base64", "byteorder", @@ -1659,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "quinn", "rustls", @@ -1670,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "base64", "chacha20poly1305", @@ -1692,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1703,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-codec", "mtp-common", @@ -1718,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "async-trait", "mtp-codec", @@ -1736,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "serde", "serde_yaml", @@ -1745,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "async-trait", "bytes", @@ -1753,6 +1750,9 @@ dependencies = [ "h3-quinn", "h3-webtransport", "http", + "http-body-util", + "hyper", + "hyper-util", "mtp-codec", "mtp-common", "mtp-crypto", @@ -1763,6 +1763,8 @@ dependencies = [ "rustls", "thiserror 2.0.19", "tokio", + "tokio-rustls", + "tokio-stream", "tracing", ] @@ -2355,9 +2357,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", @@ -2494,7 +2496,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -2966,9 +2968,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -3053,7 +3055,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -3152,9 +3154,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -3163,13 +3165,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] diff --git a/migrations/001.sql b/migrations/001_initial_schema.sql similarity index 91% rename from migrations/001.sql rename to migrations/001_initial_schema.sql index dc3a096..e2b8af5 100644 --- a/migrations/001.sql +++ b/migrations/001_initial_schema.sql @@ -1,3 +1,8 @@ +CREATE TABLE IF NOT EXISTS iotas ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL +); + CREATE TABLE IF NOT EXISTS users ( id BIGINT NOT NULL PRIMARY KEY, iota_id BIGINT NOT NULL, @@ -10,14 +15,15 @@ CREATE TABLE IF NOT EXISTS users ( sub_end BIGINT NOT NULL DEFAULT 0, public_key BLOB NOT NULL, token BLOB NOT NULL, + UNIQUE KEY uk_users_username (username), - UNIQUE KEY uk_users_iota_id (iota_id) + KEY idx_users_iota_id (iota_id), + + CONSTRAINT fk_users_iota + FOREIGN KEY (iota_id) + REFERENCES iotas (id) ); -CREATE TABLE IF NOT EXISTS iotas ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL -); CREATE TABLE IF NOT EXISTS omikrons ( id BIGINT NOT NULL PRIMARY KEY, diff --git a/migrations/002_registration_leases.sql b/migrations/002_registration_leases.sql new file mode 100644 index 0000000..a9148b1 --- /dev/null +++ b/migrations/002_registration_leases.sql @@ -0,0 +1,13 @@ +-- Registration IDs are allocated before the user row is created. Keep the +-- allocation durable and bound to the Iota that requested it so a completed +-- registration can safely be retried after any response is lost. +CREATE TABLE registration_leases ( + token CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL PRIMARY KEY, + user_id BIGINT NOT NULL UNIQUE, + iota_id BIGINT NOT NULL, + expires_at DATETIME NOT NULL, + completed_at DATETIME NULL, + CONSTRAINT fk_registration_leases_iota + FOREIGN KEY (iota_id) REFERENCES iotas (id), + INDEX idx_registration_leases_expiry (expires_at) +); diff --git a/migrations/003_registration_lease_request_id.sql b/migrations/003_registration_lease_request_id.sql new file mode 100644 index 0000000..6784177 --- /dev/null +++ b/migrations/003_registration_lease_request_id.sql @@ -0,0 +1,8 @@ +-- A relay retry uses the original MTP request ID. Make allocation idempotent +-- for that authenticated Iota/request pair, so a lost response returns the +-- same user ID and lease rather than allocating another one. +ALTER TABLE registration_leases + -- Existing leases predate retry correlation; leave their request ID NULL + -- rather than assigning a shared sentinel that could violate uniqueness. + ADD COLUMN request_id INT UNSIGNED NULL, + ADD CONSTRAINT uk_registration_leases_iota_request UNIQUE (iota_id, request_id); diff --git a/mtp-type-maps b/mtp-type-maps new file mode 160000 index 0000000..594646a --- /dev/null +++ b/mtp-type-maps @@ -0,0 +1 @@ +Subproject commit 594646ac39d986f0787aa614a99d580035a67318 diff --git a/src/config.rs b/src/config.rs index 5ba9dc2..66f5382 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,8 +9,8 @@ pub struct RateLimitConfig { pub transport_connections_per_ip: usize, } -pub fn cors_origin() -> String { - env::var("CORS_ORIGIN").unwrap_or_else(|_| "https://tensamin.net".to_string()) +pub const fn cors_origin() -> &'static str { + "*" } impl Default for RateLimitConfig { diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs index a5952f1..3eedce8 100644 --- a/src/db/iota_repo.rs +++ b/src/db/iota_repo.rs @@ -21,13 +21,31 @@ pub async fn get_iota_by_id(id: IotaId) -> Result { } pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { - let id = crate::db::user_repo::get_register_id().await?; - let iota_id = IotaId::from(id.0); - register_complete_iota(iota_id, public_key).await?; - Ok(iota_id) + for _ in 0..16 { + let id = crate::db::user_repo::get_register_id().await?; + let iota_id = IotaId::from(id.0); + match register_complete_iota(iota_id, public_key.clone()).await { + Ok(()) => return Ok(iota_id), + Err(OmegaError::Database(error)) => { + if crate::db::user_repo::is_duplicate_key(&error) { + continue; + } + return Err(OmegaError::Database(error)); + } + Err(error) => return Err(error), + } + } + Err(OmegaError::Validation( + "could not allocate a unique Iota ID".into(), + )) } pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> Result<()> { + if !crate::db::user_repo::valid_protocol_id(id.0) { + return Err(OmegaError::Validation( + "Iota ID is outside the 48-bit protocol range".into(), + )); + } sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id.0) .bind(public_key.as_bytes()) diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index ca71dd3..8554566 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -4,17 +4,94 @@ use crate::{ models::{IotaId, User, UserId}, }; use mtp::crypto::PublicKeyBundle; -use sqlx::FromRow; +use sqlx::{FromRow, Row}; + +pub const MAX_PROTOCOL_ID: i64 = (1_i64 << 48) - 1; +const ID_ALLOCATION_ATTEMPTS: usize = 16; pub async fn get_register_id() -> Result { - let bytes = *uuid::Uuid::now_v7().as_bytes(); - let id = - i64::from_be_bytes(bytes[8..].try_into().map_err(|_| { - OmegaError::Validation("generated ID has an invalid length".to_string()) - })?) & i64::MAX; + use std::time::{SystemTime, UNIX_EPOCH}; + + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + + let ts = timestamp as i64; + if ts >= 1 && ts <= MAX_PROTOCOL_ID { + return Ok(UserId::from(ts)); + } + + // Fall back to random if the timestamp is outside the 48-bit range. + let id = (rand::random::() & ((1_u64 << 48) - 1)) as i64; Ok(UserId::from(id.max(1))) } +pub fn valid_protocol_id(id: i64) -> bool { + (1..=MAX_PROTOCOL_ID).contains(&id) +} + +/// Allocate an ID that is durable, short-lived, and bound to the connected +/// Iota. `token` is presented again when completing the registration. +pub async fn allocate_registration(iota_id: IotaId, request_id: u32) -> Result<(UserId, String)> { + if !valid_protocol_id(iota_id.0) { + return Err(OmegaError::Validation( + "Iota ID is outside the 48-bit protocol range".into(), + )); + } + + for _ in 0..ID_ALLOCATION_ATTEMPTS { + let id = get_register_id().await?; + let token = uuid::Uuid::new_v4().to_string(); + let result = sqlx::query( + "INSERT INTO registration_leases (token, user_id, iota_id, request_id, expires_at) \ + VALUES (?, ?, ?, ?, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 10 MINUTE))", + ) + .bind(&token) + .bind(id.0) + .bind(iota_id.0) + .bind(request_id) + .execute(&pool().await?) + .await; + match result { + Ok(_) => return Ok((id, token)), + Err(error) if is_duplicate_key(&error) => { + let existing = sqlx::query( + "SELECT user_id, token, expires_at >= UTC_TIMESTAMP() AS current \ + FROM registration_leases WHERE iota_id = ? AND request_id = ?", + ) + .bind(iota_id.0) + .bind(request_id) + .fetch_optional(&pool().await?) + .await?; + if let Some(existing) = existing { + let current: i8 = existing.get("current"); + if current != 0 { + return Ok(( + UserId::from(existing.get::("user_id")), + existing.get::("token"), + )); + } + } + continue; + } + Err(error) => return Err(error.into()), + } + } + Err(OmegaError::Validation( + "could not allocate a unique registration ID".into(), + )) +} + +pub(crate) fn is_duplicate_key(error: &sqlx::Error) -> bool { + error.as_database_error().is_some_and(|database| { + // MySQL's generic database-error API exposes SQLSTATE (23000) as + // `code()`. The driver-specific duplicate-key number is retained in + // the diagnostic message. + database.code().as_deref() == Some("23000") && database.message().contains("1062") + }) +} + const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; const USERS_BY_IOTA_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE iota_id = ?"; @@ -179,16 +256,99 @@ pub async fn register_complete_user( public_key: PublicKeyBundle, iota_id: IotaId, token: String, + registration_token: String, ) -> Result<()> { - sqlx::query( + if !valid_protocol_id(id.0) || !valid_protocol_id(iota_id.0) { + return Err(OmegaError::Validation( + "user or Iota ID is outside the 48-bit protocol range".into(), + )); + } + if !valid_username(&username) { + return Err(OmegaError::Validation("invalid username".into())); + } + + let mut transaction = pool().await?.begin().await?; + let lease = sqlx::query( + "SELECT iota_id, completed_at IS NOT NULL AS completed, \ + expires_at >= UTC_TIMESTAMP() AS current FROM registration_leases \ + WHERE token = ? AND user_id = ? FOR UPDATE", + ) + .bind(®istration_token) + .bind(id.0) + .fetch_optional(&mut *transaction) + .await? + .ok_or_else(|| OmegaError::Validation("unknown registration lease".into()))?; + let lease_iota_id: i64 = lease.get("iota_id"); + let completed: i8 = lease.get("completed"); + let current: i8 = lease.get("current"); + if lease_iota_id != iota_id.0 || (completed == 0 && current == 0) { + return Err(OmegaError::Validation( + "expired or mismatched registration lease".into(), + )); + } + + let insert_result = sqlx::query( "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", ) .bind(id.0) - .bind(username.into_bytes()) + .bind(username.as_bytes()) .bind(public_key.as_bytes()) .bind(iota_id.0) - .bind(token.into_bytes()) - .execute(&pool().await?) - .await?; + .bind(token.as_bytes()) + .execute(&mut *transaction) + .await; + + let result: Result<()> = match insert_result { + Ok(_) => Ok(()), + Err(insert_error) => { + let existing = sqlx::query_as::<_, UserRow>(USER_BY_ID_QUERY) + .bind(id.0) + .fetch_optional(&mut *transaction) + .await?; + match existing + .map(User::try_from) + .transpose() + .map_err(OmegaError::from)? + { + Some(existing) + if existing.iota_id == iota_id + && existing.username == username + && existing.public_key.as_bytes() == public_key.as_bytes() + && existing.token == token => + { + Ok(()) + } + _ => Err(insert_error.into()), + } + } + }; + result?; + sqlx::query("UPDATE registration_leases SET completed_at = UTC_TIMESTAMP() WHERE token = ?") + .bind(®istration_token) + .execute(&mut *transaction) + .await?; + transaction.commit().await?; Ok(()) } + +fn valid_username(username: &str) -> bool { + !username.is_empty() + && username.chars().count() <= 15 + && !username.chars().any(char::is_control) + && !username.contains(['/', '\\']) +} + +#[cfg(test)] +mod tests { + use super::{MAX_PROTOCOL_ID, get_register_id, valid_protocol_id}; + + #[tokio::test] + async fn generated_registration_ids_fit_the_mtp_wire_range() { + for _ in 0..128 { + assert!(valid_protocol_id(get_register_id().await.unwrap().0)); + } + assert!(!valid_protocol_id(0)); + assert!(valid_protocol_id(MAX_PROTOCOL_ID)); + assert!(!valid_protocol_id(MAX_PROTOCOL_ID + 1)); + } +} diff --git a/src/server/api.rs b/src/server/api.rs index 6dc01aa..ed5bb80 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -15,12 +15,12 @@ use crate::server::{ validation::{parse_positive_id, validate_non_empty}, }; use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; -use crate::transport::omikron_manager::get_random_omikron; +use crate::transport::omikron_manager::{get_connected_omikron, get_random_omikron}; use crate::util::file_util::get_directory; use base64::Engine as _; use bytes::Bytes; use http::{Method, StatusCode}; -use mtp::webserver::{Http3Request, Http3Response, RouteParams}; +use mtp::webserver::{HttpRequest, HttpResponse, RouteParams}; use std::collections::BTreeMap; fn error_body(error: &OmegaError) -> String { @@ -75,20 +75,19 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } ["api", "get", "omikron", id] => { let id = parse_positive_id(id)?; - let omikron = match get_omikron_by_id(id.into()).await { - Ok(value) => value, - Err(_) => { - let fallback_id = - if let Some(fallback_id) = get_iota_primary_omikron_connection(id) { - fallback_id - } else { - let user = get_by_user_id(UserId::from(id)).await?; - get_iota_primary_omikron_connection(user.iota_id.0) - .ok_or(OmegaError::NotFound)? - }; - get_omikron_by_id(fallback_id.into()).await? - } + let omikron_id = if get_connected_omikron(id).is_some() { + id + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { + omikron_id + } else { + let user = get_by_user_id(UserId::from(id)).await?; + get_iota_primary_omikron_connection(user.iota_id.0).ok_or(OmegaError::NotFound)? }; + + // Database rows describe registered Omikrons. The public discovery + // API must expose only routes backed by a currently live transport. + get_connected_omikron(omikron_id).ok_or(OmegaError::NotFound)?; + let omikron = get_omikron_by_id(omikron_id.into()).await?; Ok(( StatusCode::OK, json(&OmikronResponse { @@ -178,7 +177,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } } -pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Response { +pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpResponse { let method = request.method; let path = request.uri.path().to_string(); if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { @@ -238,9 +237,9 @@ pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Resp } pub async fn handle_pattern( - request: Http3Request, - response: Http3Response, + request: HttpRequest, + response: HttpResponse, _params: RouteParams, -) -> Http3Response { +) -> HttpResponse { handle(request, response).await } diff --git a/src/server/index.rs b/src/server/index.rs index b121209..f128c30 100644 --- a/src/server/index.rs +++ b/src/server/index.rs @@ -1,13 +1,34 @@ use http::StatusCode; -use mtp::webserver::Http3Response; +use mtp::webserver::HttpResponse; -pub fn index_handler(response: Http3Response) -> Http3Response { +pub fn index_handler(response: HttpResponse) -> HttpResponse { let documentation = r#" Omega API Server Available Routes: -- /api/* : API endpoints for the Omega server. -- /direct/* : Resolution for shortened links. +- /api/get/omikron + Returns a randomly selected connected Omikron. +- /api/get/omikron/{id} + Returns an Omikron by ID. If {id} is a connected Iota ID or user ID, + returns that account's primary connected Omikron instead. +- /api/get/connections + Returns the current live connection map as + { "status": "success", "connections": { omikron_id: { iota_id: [user_id] } } }. +- /api/get/iota/{id} + Returns the Iota's public identity data. +- /api/get/user/{id} + Returns user profile and public identity data. +- /api/get/id/{username} + Resolves a username to its user and Iota IDs. +- /api/get/public_key + Returns Omega's public key. +- /api/download/iota_frontend + Downloads the Iota frontend archive. +- /direct/{short_key} + Resolves a shortened link with a temporary redirect. + +All IDs must be positive decimal integers. The connections endpoint reports +live in-memory state; it is empty after an Omega restart until Omikrons sync. All other routes will return this documentation. "#; diff --git a/src/server/server.rs b/src/server/server.rs index f88e267..9ac4f06 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -3,10 +3,6 @@ use mtp::webserver::WebServerConfig; pub fn build_web_config() -> Result { WebServerConfig::new() - .route( - "/", - |_request, response| async move { index_handler(response) }, - )? .route("/api/download/iota_frontend", api::handle)? .route("/api/get/omikron", api::handle)? .route("/api/get/connections", api::handle)? @@ -15,5 +11,6 @@ pub fn build_web_config() -> Result>> = Lazy::new(DashMa // UserID -> UserStatus static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); +// The legacy account map is kept for Iota-only/offline compatibility. Client +// transports are tracked independently: two devices must never overwrite each +// other's route merely because they authenticate as the same account. +static USER_SESSION_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); + pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { let mut entry = IOTA_OMIKRON_CONNECTIONS .entry(iota_id) @@ -121,13 +126,63 @@ pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { ); } +pub fn track_user_session_status( + user_id: i64, + session_id: i64, + status: UserStatus, + omikron_id: i64, +) { + USER_SESSION_STATUS_MAP.insert( + (user_id, session_id), + UserConnection { + connection_type: status, + omikron_id, + }, + ); +} + pub fn untrack_user_status(user_id: i64, omikron_id: i64) { USER_STATUS_MAP.remove_if(&user_id, |_, connection| { connection.omikron_id == omikron_id }); } +pub fn untrack_user_session_status(user_id: i64, session_id: i64, omikron_id: i64) { + USER_SESSION_STATUS_MAP.remove_if(&(user_id, session_id), |_, connection| { + connection.omikron_id == omikron_id + }); +} + +pub fn update_user_session_status(user_id: i64, status: UserStatus) { + for mut entry in USER_SESSION_STATUS_MAP.iter_mut() { + if entry.key().0 == user_id { + entry.connection_type = status.clone(); + } + } + // Preserve the account preference for legacy routes as well. + if let Some(mut entry) = USER_STATUS_MAP.get_mut(&user_id) { + entry.connection_type = status; + } +} + pub fn get_user_status(user_id: i64) -> Option { + // A connected visible session is preferred. Invisible sessions remain + // routable but are intentionally presented as offline when they are the + // only active routes. + let sessions: Vec = USER_SESSION_STATUS_MAP + .iter() + .filter(|entry| entry.key().0 == user_id) + .map(|entry| entry.value().clone()) + .collect(); + if let Some(status) = sessions + .iter() + .find(|status| status.connection_type != UserStatus::user_invisible) + { + return Some(status.clone()); + } + if let Some(status) = sessions.first() { + return Some(status.clone()); + } USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } @@ -164,6 +219,7 @@ pub async fn untrack_omikron(omikron_id: i64) { } USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); + USER_SESSION_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); for iota_id in offline_iotas { if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index 1dd10d0..adaa1a2 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -21,7 +21,18 @@ pub async fn user_connected( .and_then(UserStatus::from_str) .unwrap_or(UserStatus::user_online); if let Ok(user_id) = i64::try_from(user_id) { - user_online_tracker::track_user_status(user_id, status, omikron_id); + if let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + user_online_tracker::track_user_session_status( + user_id, session_id, status, omikron_id, + ); + } else { + user_online_tracker::track_user_status(user_id, status, omikron_id); + } } } Ok(()) @@ -34,11 +45,55 @@ pub async fn user_disconnected( ) -> OmikronResult<()> { log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - user_online_tracker::untrack_user_status(user_id as i64, omikron_id); + if let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + user_online_tracker::untrack_user_session_status( + user_id as i64, + session_id, + omikron_id, + ); + } else { + user_online_tracker::untrack_user_status(user_id as i64, omikron_id); + } } Ok(()) } +pub async fn client_changed( + _: Arc, + value: CommunicationValue, + _: i64, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + else { + return Ok(()); + }; + let Some(status) = value + .get_data(DataType::UserState) + .as_str() + .and_then(UserStatus::from_str) + else { + return Ok(()); + }; + // Connectivity is derived from routes. Clients may choose only public + // presence preferences, never server/offline states. + if matches!( + status, + UserStatus::user_offline | UserStatus::iota_offline | UserStatus::iota_online + ) { + return Ok(()); + } + user_online_tracker::update_user_session_status(user_id, status); + Ok(()) +} + pub async fn iota_connected( connection: Arc, value: CommunicationValue, diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs index 23bf3e7..3502269 100644 --- a/src/transport/handlers/register.rs +++ b/src/transport/handlers/register.rs @@ -13,13 +13,25 @@ pub async fn get_register( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let register_id = user_repo::get_register_id().await?; + let iota_id = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| user_repo::valid_protocol_id(*id)); + let Some(iota_id) = iota_id else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let (register_id, registration_token) = + user_repo::allocate_registration(IotaId::from(iota_id), value.get_id()).await?; let response = CommunicationValue::new(CommunicationType::GetRegister) .with_id(value.get_id()) .add_typed_default( DataType::UserId, DataValue::SignedNumber(register_id.0.into()), - ); + ) + .add_typed_default(DataType::RegisterId, DataValue::Str(registration_token)); connection.send(&response).await } @@ -27,10 +39,6 @@ pub async fn complete_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let iota_id = value - .get_data(DataType::IotaId) - .as_number() - .map(|id| id as i64); let public_key = value .get_data(DataType::PublicKey) .as_str() @@ -40,57 +48,25 @@ pub async fn complete_iota( .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; }; - match iota_id { - Some(iota_id) => { - match iota_repo::register_complete_iota(IotaId::from(iota_id), public_key).await { - Ok(()) => { - connection - .send( - &CommunicationValue::new(CommunicationType::Success) - .with_id(value.get_id()), - ) - .await - } - Err(error) => { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error.to_string()), - ), - ) - .await - } - } + match iota_repo::create_new_iota(public_key).await { + Ok(id) => { + connection + .send( + &CommunicationValue::new(CommunicationType::CompleteRegisterIota) + .with_id(value.get_id()) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.0.into())), + ) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await } - None => match iota_repo::create_new_iota(public_key).await { - Ok(id) => { - connection - .send( - &CommunicationValue::new(CommunicationType::CompleteRegisterIota) - .with_id(value.get_id()) - .add_typed_default( - DataType::IotaId, - DataValue::SignedNumber(id.0.into()), - ), - ) - .await - } - Err(error) => { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error.to_string()), - ), - ) - .await - } - }, } } @@ -101,7 +77,8 @@ pub async fn complete_user( let user_id = value .get_data(DataType::UserId) .as_number() - .map(|id| id as i64); + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| user_repo::valid_protocol_id(*id)); let username = value .get_data(DataType::Username) .as_str() @@ -114,22 +91,44 @@ pub async fn complete_user( .get_data(DataType::ResetToken) .as_str() .map(str::to_owned); - let Some((user_id, username, public_key, reset_token)) = user_id + let registration_token = value + .get_data(DataType::RegisterId) + .as_str() + .filter(|token| uuid::Uuid::parse_str(token).is_ok()) + .map(str::to_owned); + let Some((user_id, username, public_key, reset_token, registration_token)) = user_id .zip(username) .zip(public_key) .zip(reset_token) - .map(|(((id, name), key), token)| (id, name, key, token)) + .zip(registration_token) + .map(|((((id, name), key), token), registration_token)| { + (id, name, key, token, registration_token) + }) else { return connection .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; }; + // Omikron supplies the authenticated Iota ID in the payload. The lease + // check below binds completion to that Iota rather than trusting sender. + let iota_id = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| user_repo::valid_protocol_id(*id)); + let Some(iota_id) = iota_id else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + match user_repo::register_complete_user( UserId::from(user_id), username, public_key, - IotaId::from(value.get_sender() as i64), + IotaId::from(iota_id), reset_token, + registration_token, ) .await { diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 7095f9d..d48120c 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -91,10 +91,29 @@ impl OmikronConnection { .retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE); } })); - while let Ok(value) = receiver.receive().await { - if let Err(error) = self.clone().process_message(value).await { - log_err!(0, PrintType::Omega, "Error processing message: {}", error); - if matches!(error, crate::error::OmegaError::NotConnected) { + loop { + match receiver.receive().await { + Ok(value) => { + if let Err(error) = self.clone().process_message(value).await { + log_err!( + self.id as i64, + PrintType::Omega, + "Error processing Omikron message: {}", + error + ); + if matches!(error, crate::error::OmegaError::NotConnected) { + break; + } + } + } + Err(error) => { + log_err!( + self.id as i64, + PrintType::Omega, + "Omikron receive loop ended: {}; transport close reason: {:?}", + error, + receiver.close_reason() + ); break; } } @@ -133,6 +152,9 @@ impl OmikronConnection { Some(CommunicationType::UserDisconnected) => { crate::transport::handlers::presence::user_disconnected(self, value, id).await } + Some(CommunicationType::ClientChanged) => { + crate::transport::handlers::presence::client_changed(self, value, id).await + } Some(CommunicationType::IotaConnected) => { crate::transport::handlers::presence::iota_connected(self, value, id).await } @@ -259,40 +281,38 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { pub async fn start(port: u16) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "cert.pem")?; let key_pem = load_file_vec("certs", "key.pem")?; - let web_config = server::server::build_web_config()?; - let host_config = HostConfig::new( - IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), - port, - cert_pem, - key_pem, - ) - .with_policy(Policy { - send_mode: SendMode::SingleStreamPerMessage, - max_message_size: 1_000_000_000, - handshake_max_message_size: 1_000_000, - close_frame_len: u32::MAX, - application_close_code: 0, - open_stream_timeout: Duration::from_millis(2_000), - write_timeout: Duration::from_millis(2_000), - accept_stream_timeout: Duration::from_millis(10_000), - read_timeout: Duration::from_millis(30_000), - keep_alive_interval: Some(Duration::from_secs(6)), - max_idle_timeout: Some(Duration::from_secs(30)), - force_close_delay: Duration::from_millis(300), - receiver_queue_capacity: 1000, - max_concurrent_stream_tasks: 10, - persistent_stream_max_retries: 5, - persistent_stream_retry_backoff: Duration::from_secs(5), - max_frames_per_stream: None, - }) - .with_authentication( - load_keyring(), - Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))), - Box::new(|key, description| Box::pin(complete_register(key, description))), - ) - .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); + let web_config = server::server::build_web_config()? + .serve_tcp_https(true) + .max_tcp_connections(256); + let ip = IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)); + let host_config = HostConfig::new(ip, port, cert_pem, key_pem) + .with_policy(Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + handshake_max_message_size: 1_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(5_000), + write_timeout: Duration::from_millis(5_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + receiver_queue_capacity: 1000, + max_concurrent_stream_tasks: 64, + persistent_stream_max_retries: 5, + persistent_stream_retry_backoff: Duration::from_secs(5), + max_frames_per_stream: None, + }) + .with_authentication( + load_keyring(), + Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))), + Box::new(|key, description| Box::pin(complete_register(key, description))), + ) + .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); let mut server = MTPWebServer::new(host_config, web_config).await?; - log!("OmegaServer listening on port {}", port); + log!("OmegaServer listening on {}:{}", ip.to_string(), port); loop { let mut conn = match server.accept().await { Ok(Some(conn)) => conn, diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 2de4ea3..0b2f66c 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -26,12 +26,18 @@ pub async fn remove_omikron(omikron_id: i64) { OMIKRON_CONNECTIONS.remove(&omikron_id); } +pub fn get_connected_omikron(omikron_id: i64) -> Option> { + OMIKRON_CONNECTIONS + .get(&omikron_id) + .map(|connection| connection.clone()) +} + pub async fn get_random_omikron() -> Result, ()> { let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); if let Some(key) = keys.into_iter().choose(&mut rand::rng()) { - if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { - return Ok(entry.clone()); + if let Some(connection) = get_connected_omikron(key) { + return Ok(connection); } } diff --git a/src/util/logger.rs b/src/util/logger.rs index 39eb933..4276cc0 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -244,8 +244,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String { parts.push(format!("> {}", receiver)); } - let comm_type = cv.get_type().to_string(); - parts.push(format!("{}", comm_type)); + let comm_type = cv + .get_comm_type_enum() + .map(|kind| kind.to_string()) + .unwrap_or_else(|| cv.get_type().to_string()); + parts.push(format!("{} (id={})", comm_type, cv.get_id())); let data = cv.data(); diff --git a/type-maps.yaml b/type-maps.yaml deleted file mode 100644 index ce3d5e6..0000000 --- a/type-maps.yaml +++ /dev/null @@ -1,269 +0,0 @@ -protocol_version: "1.0" - -# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended - -# Fixed CommunicationType markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorBadVersion: 2 -# Disconnect: 3 -# Redirect: 4 -# Shutdown: 5 -# BadRequest: 6 -# Unauthorized: 7 -# Forbidden: 8 -# NotFound: 9 -# TooManyRequests: 10 -# InternalServerError: 11 -# BadGateway: 12 -# ServiceUnavailable: 13 -# GatewayTimeout: 14 -# Identification: 15 -# IdentificationResponse: 16 -# Register: 17 -# RegisterResponse: 18 -# Ping: 19 -# Pong: 20 - -# Fixed Data Type markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorMessage: 2 -# Version: 3 -# Description: 4 -# Timestamp: 5 -# Id: 6 -# ClientNonce: 7 -# ServerNonce: 8 -# PublicKeys: 9 -# Signature: 10 -# Connected: 11 - -type_maps: - "1.0": - CommunicationTypes: - ErrorProtocol: 33 - ErrorAnonymous: 34 - ErrorInternal: 35 - ErrorInvalidData: 36 - ErrorInvalidUserId: 37 - ErrorInvalidOmikronId: 38 - ErrorNotFound: 39 - ErrorNotAuthenticated: 40 - ErrorNoIota: 41 - ErrorInvalidChallenge: 42 - ErrorInvalidSecret: 43 - ErrorInvalidPrivateKey: 44 - ErrorInvalidPublicKey: 45 - ErrorNoUserId: 46 - ErrorNoCallId: 47 - ErrorInvalidCallId: 48 - Success: 49 - ShortenLink: 50 - SettingsSave: 51 - SettingsLoad: 52 - SettingsList: 53 - GlobalSettingsSave: 54 - GlobalSettingsLoad: 55 - Message: 56 - MessageState: 57 - MessageSend: 58 - MessageLive: 59 - MessageOtherIota: 60 - MessageChunk: 61 - MessageGet: 143 - MessagesGet: 62 - PushNotification: 63 - ReadNotification: 64 - GetNotifications: 65 - TauriIdentification: 66 - ChangeConfirm: 67 - ConfirmReceive: 68 - ConfirmRead: 69 - GetChats: 70 - GetStates: 71 - AddCommunity: 72 - RemoveCommunity: 73 - GetCommunities: 74 - RegisterIota: 81 - RegisterIotaSuccess: 82 - AddConversation: 85 - SendChat: 86 - ClientChanged: 87 - ClientConnected: 88 - ClientDisconnected: 89 - ClientClosed: 90 - PublicKey: 91 - PrivateKey: 92 - WebrtcSdp: 93 - WebrtcIce: 94 - StartStream: 95 - EndStream: 96 - WatchStream: 97 - CallToken: 98 - CallInvite: 99 - CallDisconnectUser: 100 - CallTimeoutUser: 101 - CallSetAnonymousJoining: 102 - CallData: 103 - EndCall: 104 - Function: 105 - Update: 106 - CreateUser: 107 - RhoUpdate: 108 - UserConnected: 109 - UserDisconnected: 110 - IotaConnected: 111 - IotaDisconnected: 112 - SyncClientIotaStatus: 113 - GetUserData: 114 - GetIotaData: 115 - IotaUserData: 116 - ChangeUserData: 117 - ChangeIotaData: 118 - GetRegister: 119 - CompleteRegisterUser: 120 - CompleteRegisterIota: 121 - DeleteUser: 122 - DeleteIota: 123 - StartRegister: 124 - CompleteRegister: 125 - GetApp: 126 - CreateApp: 127 - DeleteApp: 128 - SaveAppData: 129 - LoadAppData: 130 - AppIdentification: 131 - AppChallenge: 132 - AppChallengeResponse: 133 - AppIdentificationResponse: 134 - LoadTxtRecord: 135 - ErrorNotSet: 136 - SetChatSecret: 139 - GetChatSecret: 140 - ChatSecretResponse: 141 - ChatSecretForward: 142 - MessageEditLive: 144 - MessageEdit: 145 - MessageReactionAdd: 146 - MessageReactionRemove: 147 - MessageReactionLive: 148 - MessageDeleteLive: 150 - DataTypes: - ErrorType: 32 - ErrorProtocol: 33 - AcceptedIds: 34 - Uuid: 35 - RegisterId: 36 - Link: 37 - Settings: 38 - SettingsName: 39 - ChatPartnerId: 40 - ChatPartnerName: 41 - IotaId: 42 - UserId: 43 - UserIds: 44 - IotaIds: 45 - UserState: 46 - UserStates: 47 - UserPings: 48 - CallState: 49 - ScreenShare: 50 - PrivateKeyHash: 51 - # Accepted: 52 now part of default MTP - AcceptedProfiles: 53 - DeniedProfiles: 54 - Content: 55 - Messages: 56 - Notifications: 57 - SendTime: 58 - GetTime: 59 - GetVariant: 60 - SharedSecretOwn: 61 - SharedSecretOther: 62 - SharedSecretSign: 63 - SharedSecret: 64 - CallId: 65 - CallToken: 66 - CallSecret: 67 - Untill: 68 - Enabled: 69 - StartDate: 70 - EndDate: 71 - ReceiverId: 72 - SenderId: 73 - Signed: 75 - Message: 76 - MessageState: 77 - LastPing: 78 - PingIota: 79 - PingClients: 80 - Matches: 81 - Omikron: 82 - Offset: 83 - Amount: 84 - Position: 85 - Name: 86 - Path: 87 - Codec: 88 - Function: 89 - Payload: 90 - Result: 91 - Interactables: 92 - WantToWatch: 93 - Watcher: 94 - CreatedAt: 95 - Username: 96 - Display: 97 - Avatar: 98 - About: 99 - Status: 100 - PublicKey: 101 - SubLevel: 102 - SubEnd: 103 - CommunityAddress: 104 - CommunityTitle: 106 - Communities: 107 - RhoConnections: 108 - User: 109 - OnlineStatus: 110 - OmikronId: 111 - OmikronConnections: 112 - ResetToken: 113 - NewToken: 114 - CallInvited: 115 - CallMembers: 116 - Calls: 117 - Timeout: 118 - HasAdmin: 119 - LastMessageAt: 120 - Height: 121 - SentBySelf: 122 - SessionId: 123 - Contacts: 124 - LastMessage: 125 - AppIdentifier: 127 - AppPrivateKey: 128 - AppPublicKey: 129 - AppSession: 130 - AppData: 131 - TauriToken: 132 - Challenge: 133 - EncryptedPayload: 134 - SecurePayload: 135 - DeviceId: 136 - ClientId: 137 - SecretId: 142 - VersionNumber: 143 - EncryptedSecret: 144 - WrappingScheme: 146 - UpdatedAt: 147 - ChatId: 148 - KemCiphertext: 149 - SenderUserId: 152 - RecipientUserId: 153 - Recipients: 154 - Edited: 155 - Reactions: 156 - Reaction: 157 - ReplyId: 158 From 4f7260419a13d1e9256c96fe293778d8504ba526 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 27 Jul 2026 20:36:23 +0200 Subject: [PATCH 190/220] [Fix] Stability --- .cargo/config.toml | 2 +- .gitmodules | 3 + Cargo.lock | 85 +++--- .../{001.sql => 001_initial_schema.sql} | 16 +- migrations/002_registration_leases.sql | 13 + .../003_registration_lease_request_id.sql | 8 + mtp-type-maps | 1 + src/config.rs | 4 +- src/db/iota_repo.rs | 26 +- src/db/user_repo.rs | 182 +++++++++++- src/server/api.rs | 37 ++- src/server/index.rs | 29 +- src/server/server.rs | 7 +- src/sql/user_online_tracker.rs | 56 ++++ src/transport/handlers/presence.rs | 59 +++- src/transport/handlers/register.rs | 119 ++++---- src/transport/omikron_connection.rs | 94 +++--- src/transport/omikron_manager.rs | 10 +- src/util/logger.rs | 7 +- type-maps.yaml | 269 ------------------ 20 files changed, 563 insertions(+), 464 deletions(-) create mode 100644 .gitmodules rename migrations/{001.sql => 001_initial_schema.sql} (91%) create mode 100644 migrations/002_registration_leases.sql create mode 100644 migrations/003_registration_lease_request_id.sql create mode 160000 mtp-type-maps delete mode 100644 type-maps.yaml diff --git a/.cargo/config.toml b/.cargo/config.toml index d363b83..46adaad 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ [env] -MTP_TYPE_MAPS = { value = "type-maps.yaml", relative = true } +MTP_TYPE_MAPS = { value = "mtp-type-maps/type-maps.yaml", relative = true } diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..1e9ee12 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "mtp-type-maps"] + path = mtp-type-maps + url = https://git.methanium.net/tensamin/mtp-type-maps diff --git a/Cargo.lock b/Cargo.lock index a20fbae..8c1774a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,9 +20,9 @@ dependencies = [ [[package]] name = "aes" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fc76eaeac4c9164506c466d4ffdd8ec9d0c5bf57ee97177c4d8eceb3a0e138" +checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ "cipher 0.5.2", "cpubits", @@ -91,7 +91,7 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -217,9 +217,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" dependencies = [ "find-msvc-tools", "jobserver", @@ -320,15 +320,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "const-oid" version = "0.9.6" @@ -641,9 +632,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" dependencies = [ "serde", ] @@ -686,11 +677,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -1124,6 +1114,12 @@ 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 = "hybrid-array" version = "0.4.13" @@ -1148,6 +1144,7 @@ dependencies = [ "http", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -1444,9 +1441,9 @@ checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" [[package]] name = "libc" -version = "0.2.186" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libm" @@ -1617,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-client", "mtp-codec", @@ -1633,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-codec", "mtp-common", @@ -1646,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "base64", "byteorder", @@ -1659,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "quinn", "rustls", @@ -1670,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "base64", "chacha20poly1305", @@ -1692,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1703,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "mtp-codec", "mtp-common", @@ -1718,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "async-trait", "mtp-codec", @@ -1736,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "serde", "serde_yaml", @@ -1745,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#04760fd88d2bc3adf548a9ec532fa228227f0a49" +source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" dependencies = [ "async-trait", "bytes", @@ -1753,6 +1750,9 @@ dependencies = [ "h3-quinn", "h3-webtransport", "http", + "http-body-util", + "hyper", + "hyper-util", "mtp-codec", "mtp-common", "mtp-crypto", @@ -1763,6 +1763,8 @@ dependencies = [ "rustls", "thiserror 2.0.19", "tokio", + "tokio-rustls", + "tokio-stream", "tracing", ] @@ -2355,9 +2357,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "web-time", "zeroize", @@ -2494,7 +2496,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -2966,9 +2968,9 @@ dependencies = [ [[package]] name = "syn" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" dependencies = [ "proc-macro2", "quote", @@ -3053,7 +3055,7 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" dependencies = [ "proc-macro2", "quote", - "syn 3.0.2", + "syn 3.0.3", ] [[package]] @@ -3152,9 +3154,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" dependencies = [ "futures-core", "pin-project-lite", @@ -3163,13 +3165,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] diff --git a/migrations/001.sql b/migrations/001_initial_schema.sql similarity index 91% rename from migrations/001.sql rename to migrations/001_initial_schema.sql index dc3a096..e2b8af5 100644 --- a/migrations/001.sql +++ b/migrations/001_initial_schema.sql @@ -1,3 +1,8 @@ +CREATE TABLE IF NOT EXISTS iotas ( + id BIGINT NOT NULL PRIMARY KEY, + public_key BLOB NOT NULL +); + CREATE TABLE IF NOT EXISTS users ( id BIGINT NOT NULL PRIMARY KEY, iota_id BIGINT NOT NULL, @@ -10,14 +15,15 @@ CREATE TABLE IF NOT EXISTS users ( sub_end BIGINT NOT NULL DEFAULT 0, public_key BLOB NOT NULL, token BLOB NOT NULL, + UNIQUE KEY uk_users_username (username), - UNIQUE KEY uk_users_iota_id (iota_id) + KEY idx_users_iota_id (iota_id), + + CONSTRAINT fk_users_iota + FOREIGN KEY (iota_id) + REFERENCES iotas (id) ); -CREATE TABLE IF NOT EXISTS iotas ( - id BIGINT NOT NULL PRIMARY KEY, - public_key BLOB NOT NULL -); CREATE TABLE IF NOT EXISTS omikrons ( id BIGINT NOT NULL PRIMARY KEY, diff --git a/migrations/002_registration_leases.sql b/migrations/002_registration_leases.sql new file mode 100644 index 0000000..a9148b1 --- /dev/null +++ b/migrations/002_registration_leases.sql @@ -0,0 +1,13 @@ +-- Registration IDs are allocated before the user row is created. Keep the +-- allocation durable and bound to the Iota that requested it so a completed +-- registration can safely be retried after any response is lost. +CREATE TABLE registration_leases ( + token CHAR(36) CHARACTER SET ascii COLLATE ascii_bin NOT NULL PRIMARY KEY, + user_id BIGINT NOT NULL UNIQUE, + iota_id BIGINT NOT NULL, + expires_at DATETIME NOT NULL, + completed_at DATETIME NULL, + CONSTRAINT fk_registration_leases_iota + FOREIGN KEY (iota_id) REFERENCES iotas (id), + INDEX idx_registration_leases_expiry (expires_at) +); diff --git a/migrations/003_registration_lease_request_id.sql b/migrations/003_registration_lease_request_id.sql new file mode 100644 index 0000000..6784177 --- /dev/null +++ b/migrations/003_registration_lease_request_id.sql @@ -0,0 +1,8 @@ +-- A relay retry uses the original MTP request ID. Make allocation idempotent +-- for that authenticated Iota/request pair, so a lost response returns the +-- same user ID and lease rather than allocating another one. +ALTER TABLE registration_leases + -- Existing leases predate retry correlation; leave their request ID NULL + -- rather than assigning a shared sentinel that could violate uniqueness. + ADD COLUMN request_id INT UNSIGNED NULL, + ADD CONSTRAINT uk_registration_leases_iota_request UNIQUE (iota_id, request_id); diff --git a/mtp-type-maps b/mtp-type-maps new file mode 160000 index 0000000..594646a --- /dev/null +++ b/mtp-type-maps @@ -0,0 +1 @@ +Subproject commit 594646ac39d986f0787aa614a99d580035a67318 diff --git a/src/config.rs b/src/config.rs index 5ba9dc2..66f5382 100644 --- a/src/config.rs +++ b/src/config.rs @@ -9,8 +9,8 @@ pub struct RateLimitConfig { pub transport_connections_per_ip: usize, } -pub fn cors_origin() -> String { - env::var("CORS_ORIGIN").unwrap_or_else(|_| "https://tensamin.net".to_string()) +pub const fn cors_origin() -> &'static str { + "*" } impl Default for RateLimitConfig { diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs index a5952f1..3eedce8 100644 --- a/src/db/iota_repo.rs +++ b/src/db/iota_repo.rs @@ -21,13 +21,31 @@ pub async fn get_iota_by_id(id: IotaId) -> Result { } pub async fn create_new_iota(public_key: PublicKeyBundle) -> Result { - let id = crate::db::user_repo::get_register_id().await?; - let iota_id = IotaId::from(id.0); - register_complete_iota(iota_id, public_key).await?; - Ok(iota_id) + for _ in 0..16 { + let id = crate::db::user_repo::get_register_id().await?; + let iota_id = IotaId::from(id.0); + match register_complete_iota(iota_id, public_key.clone()).await { + Ok(()) => return Ok(iota_id), + Err(OmegaError::Database(error)) => { + if crate::db::user_repo::is_duplicate_key(&error) { + continue; + } + return Err(OmegaError::Database(error)); + } + Err(error) => return Err(error), + } + } + Err(OmegaError::Validation( + "could not allocate a unique Iota ID".into(), + )) } pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> Result<()> { + if !crate::db::user_repo::valid_protocol_id(id.0) { + return Err(OmegaError::Validation( + "Iota ID is outside the 48-bit protocol range".into(), + )); + } sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id.0) .bind(public_key.as_bytes()) diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index ca71dd3..8554566 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -4,17 +4,94 @@ use crate::{ models::{IotaId, User, UserId}, }; use mtp::crypto::PublicKeyBundle; -use sqlx::FromRow; +use sqlx::{FromRow, Row}; + +pub const MAX_PROTOCOL_ID: i64 = (1_i64 << 48) - 1; +const ID_ALLOCATION_ATTEMPTS: usize = 16; pub async fn get_register_id() -> Result { - let bytes = *uuid::Uuid::now_v7().as_bytes(); - let id = - i64::from_be_bytes(bytes[8..].try_into().map_err(|_| { - OmegaError::Validation("generated ID has an invalid length".to_string()) - })?) & i64::MAX; + use std::time::{SystemTime, UNIX_EPOCH}; + + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs()) + .unwrap_or(0); + + let ts = timestamp as i64; + if ts >= 1 && ts <= MAX_PROTOCOL_ID { + return Ok(UserId::from(ts)); + } + + // Fall back to random if the timestamp is outside the 48-bit range. + let id = (rand::random::() & ((1_u64 << 48) - 1)) as i64; Ok(UserId::from(id.max(1))) } +pub fn valid_protocol_id(id: i64) -> bool { + (1..=MAX_PROTOCOL_ID).contains(&id) +} + +/// Allocate an ID that is durable, short-lived, and bound to the connected +/// Iota. `token` is presented again when completing the registration. +pub async fn allocate_registration(iota_id: IotaId, request_id: u32) -> Result<(UserId, String)> { + if !valid_protocol_id(iota_id.0) { + return Err(OmegaError::Validation( + "Iota ID is outside the 48-bit protocol range".into(), + )); + } + + for _ in 0..ID_ALLOCATION_ATTEMPTS { + let id = get_register_id().await?; + let token = uuid::Uuid::new_v4().to_string(); + let result = sqlx::query( + "INSERT INTO registration_leases (token, user_id, iota_id, request_id, expires_at) \ + VALUES (?, ?, ?, ?, DATE_ADD(UTC_TIMESTAMP(), INTERVAL 10 MINUTE))", + ) + .bind(&token) + .bind(id.0) + .bind(iota_id.0) + .bind(request_id) + .execute(&pool().await?) + .await; + match result { + Ok(_) => return Ok((id, token)), + Err(error) if is_duplicate_key(&error) => { + let existing = sqlx::query( + "SELECT user_id, token, expires_at >= UTC_TIMESTAMP() AS current \ + FROM registration_leases WHERE iota_id = ? AND request_id = ?", + ) + .bind(iota_id.0) + .bind(request_id) + .fetch_optional(&pool().await?) + .await?; + if let Some(existing) = existing { + let current: i8 = existing.get("current"); + if current != 0 { + return Ok(( + UserId::from(existing.get::("user_id")), + existing.get::("token"), + )); + } + } + continue; + } + Err(error) => return Err(error.into()), + } + } + Err(OmegaError::Validation( + "could not allocate a unique registration ID".into(), + )) +} + +pub(crate) fn is_duplicate_key(error: &sqlx::Error) -> bool { + error.as_database_error().is_some_and(|database| { + // MySQL's generic database-error API exposes SQLSTATE (23000) as + // `code()`. The driver-specific duplicate-key number is retained in + // the diagnostic message. + database.code().as_deref() == Some("23000") && database.message().contains("1062") + }) +} + const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; const USERS_BY_IOTA_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE iota_id = ?"; @@ -179,16 +256,99 @@ pub async fn register_complete_user( public_key: PublicKeyBundle, iota_id: IotaId, token: String, + registration_token: String, ) -> Result<()> { - sqlx::query( + if !valid_protocol_id(id.0) || !valid_protocol_id(iota_id.0) { + return Err(OmegaError::Validation( + "user or Iota ID is outside the 48-bit protocol range".into(), + )); + } + if !valid_username(&username) { + return Err(OmegaError::Validation("invalid username".into())); + } + + let mut transaction = pool().await?.begin().await?; + let lease = sqlx::query( + "SELECT iota_id, completed_at IS NOT NULL AS completed, \ + expires_at >= UTC_TIMESTAMP() AS current FROM registration_leases \ + WHERE token = ? AND user_id = ? FOR UPDATE", + ) + .bind(®istration_token) + .bind(id.0) + .fetch_optional(&mut *transaction) + .await? + .ok_or_else(|| OmegaError::Validation("unknown registration lease".into()))?; + let lease_iota_id: i64 = lease.get("iota_id"); + let completed: i8 = lease.get("completed"); + let current: i8 = lease.get("current"); + if lease_iota_id != iota_id.0 || (completed == 0 && current == 0) { + return Err(OmegaError::Validation( + "expired or mismatched registration lease".into(), + )); + } + + let insert_result = sqlx::query( "INSERT INTO users (id, username, public_key, iota_id, token) VALUES (?, ?, ?, ?, ?)", ) .bind(id.0) - .bind(username.into_bytes()) + .bind(username.as_bytes()) .bind(public_key.as_bytes()) .bind(iota_id.0) - .bind(token.into_bytes()) - .execute(&pool().await?) - .await?; + .bind(token.as_bytes()) + .execute(&mut *transaction) + .await; + + let result: Result<()> = match insert_result { + Ok(_) => Ok(()), + Err(insert_error) => { + let existing = sqlx::query_as::<_, UserRow>(USER_BY_ID_QUERY) + .bind(id.0) + .fetch_optional(&mut *transaction) + .await?; + match existing + .map(User::try_from) + .transpose() + .map_err(OmegaError::from)? + { + Some(existing) + if existing.iota_id == iota_id + && existing.username == username + && existing.public_key.as_bytes() == public_key.as_bytes() + && existing.token == token => + { + Ok(()) + } + _ => Err(insert_error.into()), + } + } + }; + result?; + sqlx::query("UPDATE registration_leases SET completed_at = UTC_TIMESTAMP() WHERE token = ?") + .bind(®istration_token) + .execute(&mut *transaction) + .await?; + transaction.commit().await?; Ok(()) } + +fn valid_username(username: &str) -> bool { + !username.is_empty() + && username.chars().count() <= 15 + && !username.chars().any(char::is_control) + && !username.contains(['/', '\\']) +} + +#[cfg(test)] +mod tests { + use super::{MAX_PROTOCOL_ID, get_register_id, valid_protocol_id}; + + #[tokio::test] + async fn generated_registration_ids_fit_the_mtp_wire_range() { + for _ in 0..128 { + assert!(valid_protocol_id(get_register_id().await.unwrap().0)); + } + assert!(!valid_protocol_id(0)); + assert!(valid_protocol_id(MAX_PROTOCOL_ID)); + assert!(!valid_protocol_id(MAX_PROTOCOL_ID + 1)); + } +} diff --git a/src/server/api.rs b/src/server/api.rs index 6dc01aa..ed5bb80 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -15,12 +15,12 @@ use crate::server::{ validation::{parse_positive_id, validate_non_empty}, }; use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; -use crate::transport::omikron_manager::get_random_omikron; +use crate::transport::omikron_manager::{get_connected_omikron, get_random_omikron}; use crate::util::file_util::get_directory; use base64::Engine as _; use bytes::Bytes; use http::{Method, StatusCode}; -use mtp::webserver::{Http3Request, Http3Response, RouteParams}; +use mtp::webserver::{HttpRequest, HttpResponse, RouteParams}; use std::collections::BTreeMap; fn error_body(error: &OmegaError) -> String { @@ -75,20 +75,19 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } ["api", "get", "omikron", id] => { let id = parse_positive_id(id)?; - let omikron = match get_omikron_by_id(id.into()).await { - Ok(value) => value, - Err(_) => { - let fallback_id = - if let Some(fallback_id) = get_iota_primary_omikron_connection(id) { - fallback_id - } else { - let user = get_by_user_id(UserId::from(id)).await?; - get_iota_primary_omikron_connection(user.iota_id.0) - .ok_or(OmegaError::NotFound)? - }; - get_omikron_by_id(fallback_id.into()).await? - } + let omikron_id = if get_connected_omikron(id).is_some() { + id + } else if let Some(omikron_id) = get_iota_primary_omikron_connection(id) { + omikron_id + } else { + let user = get_by_user_id(UserId::from(id)).await?; + get_iota_primary_omikron_connection(user.iota_id.0).ok_or(OmegaError::NotFound)? }; + + // Database rows describe registered Omikrons. The public discovery + // API must expose only routes backed by a currently live transport. + get_connected_omikron(omikron_id).ok_or(OmegaError::NotFound)?; + let omikron = get_omikron_by_id(omikron_id.into()).await?; Ok(( StatusCode::OK, json(&OmikronResponse { @@ -178,7 +177,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } } -pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Response { +pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpResponse { let method = request.method; let path = request.uri.path().to_string(); if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { @@ -238,9 +237,9 @@ pub async fn handle(request: Http3Request, response: Http3Response) -> Http3Resp } pub async fn handle_pattern( - request: Http3Request, - response: Http3Response, + request: HttpRequest, + response: HttpResponse, _params: RouteParams, -) -> Http3Response { +) -> HttpResponse { handle(request, response).await } diff --git a/src/server/index.rs b/src/server/index.rs index b121209..f128c30 100644 --- a/src/server/index.rs +++ b/src/server/index.rs @@ -1,13 +1,34 @@ use http::StatusCode; -use mtp::webserver::Http3Response; +use mtp::webserver::HttpResponse; -pub fn index_handler(response: Http3Response) -> Http3Response { +pub fn index_handler(response: HttpResponse) -> HttpResponse { let documentation = r#" Omega API Server Available Routes: -- /api/* : API endpoints for the Omega server. -- /direct/* : Resolution for shortened links. +- /api/get/omikron + Returns a randomly selected connected Omikron. +- /api/get/omikron/{id} + Returns an Omikron by ID. If {id} is a connected Iota ID or user ID, + returns that account's primary connected Omikron instead. +- /api/get/connections + Returns the current live connection map as + { "status": "success", "connections": { omikron_id: { iota_id: [user_id] } } }. +- /api/get/iota/{id} + Returns the Iota's public identity data. +- /api/get/user/{id} + Returns user profile and public identity data. +- /api/get/id/{username} + Resolves a username to its user and Iota IDs. +- /api/get/public_key + Returns Omega's public key. +- /api/download/iota_frontend + Downloads the Iota frontend archive. +- /direct/{short_key} + Resolves a shortened link with a temporary redirect. + +All IDs must be positive decimal integers. The connections endpoint reports +live in-memory state; it is empty after an Omega restart until Omikrons sync. All other routes will return this documentation. "#; diff --git a/src/server/server.rs b/src/server/server.rs index f88e267..9ac4f06 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -3,10 +3,6 @@ use mtp::webserver::WebServerConfig; pub fn build_web_config() -> Result { WebServerConfig::new() - .route( - "/", - |_request, response| async move { index_handler(response) }, - )? .route("/api/download/iota_frontend", api::handle)? .route("/api/get/omikron", api::handle)? .route("/api/get/connections", api::handle)? @@ -15,5 +11,6 @@ pub fn build_web_config() -> Result>> = Lazy::new(DashMa // UserID -> UserStatus static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); +// The legacy account map is kept for Iota-only/offline compatibility. Client +// transports are tracked independently: two devices must never overwrite each +// other's route merely because they authenticate as the same account. +static USER_SESSION_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); + pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { let mut entry = IOTA_OMIKRON_CONNECTIONS .entry(iota_id) @@ -121,13 +126,63 @@ pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { ); } +pub fn track_user_session_status( + user_id: i64, + session_id: i64, + status: UserStatus, + omikron_id: i64, +) { + USER_SESSION_STATUS_MAP.insert( + (user_id, session_id), + UserConnection { + connection_type: status, + omikron_id, + }, + ); +} + pub fn untrack_user_status(user_id: i64, omikron_id: i64) { USER_STATUS_MAP.remove_if(&user_id, |_, connection| { connection.omikron_id == omikron_id }); } +pub fn untrack_user_session_status(user_id: i64, session_id: i64, omikron_id: i64) { + USER_SESSION_STATUS_MAP.remove_if(&(user_id, session_id), |_, connection| { + connection.omikron_id == omikron_id + }); +} + +pub fn update_user_session_status(user_id: i64, status: UserStatus) { + for mut entry in USER_SESSION_STATUS_MAP.iter_mut() { + if entry.key().0 == user_id { + entry.connection_type = status.clone(); + } + } + // Preserve the account preference for legacy routes as well. + if let Some(mut entry) = USER_STATUS_MAP.get_mut(&user_id) { + entry.connection_type = status; + } +} + pub fn get_user_status(user_id: i64) -> Option { + // A connected visible session is preferred. Invisible sessions remain + // routable but are intentionally presented as offline when they are the + // only active routes. + let sessions: Vec = USER_SESSION_STATUS_MAP + .iter() + .filter(|entry| entry.key().0 == user_id) + .map(|entry| entry.value().clone()) + .collect(); + if let Some(status) = sessions + .iter() + .find(|status| status.connection_type != UserStatus::user_invisible) + { + return Some(status.clone()); + } + if let Some(status) = sessions.first() { + return Some(status.clone()); + } USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) } @@ -164,6 +219,7 @@ pub async fn untrack_omikron(omikron_id: i64) { } USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); + USER_SESSION_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); for iota_id in offline_iotas { if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index 1dd10d0..adaa1a2 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -21,7 +21,18 @@ pub async fn user_connected( .and_then(UserStatus::from_str) .unwrap_or(UserStatus::user_online); if let Ok(user_id) = i64::try_from(user_id) { - user_online_tracker::track_user_status(user_id, status, omikron_id); + if let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + user_online_tracker::track_user_session_status( + user_id, session_id, status, omikron_id, + ); + } else { + user_online_tracker::track_user_status(user_id, status, omikron_id); + } } } Ok(()) @@ -34,11 +45,55 @@ pub async fn user_disconnected( ) -> OmikronResult<()> { log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - user_online_tracker::untrack_user_status(user_id as i64, omikron_id); + if let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + user_online_tracker::untrack_user_session_status( + user_id as i64, + session_id, + omikron_id, + ); + } else { + user_online_tracker::untrack_user_status(user_id as i64, omikron_id); + } } Ok(()) } +pub async fn client_changed( + _: Arc, + value: CommunicationValue, + _: i64, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + else { + return Ok(()); + }; + let Some(status) = value + .get_data(DataType::UserState) + .as_str() + .and_then(UserStatus::from_str) + else { + return Ok(()); + }; + // Connectivity is derived from routes. Clients may choose only public + // presence preferences, never server/offline states. + if matches!( + status, + UserStatus::user_offline | UserStatus::iota_offline | UserStatus::iota_online + ) { + return Ok(()); + } + user_online_tracker::update_user_session_status(user_id, status); + Ok(()) +} + pub async fn iota_connected( connection: Arc, value: CommunicationValue, diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs index 23bf3e7..3502269 100644 --- a/src/transport/handlers/register.rs +++ b/src/transport/handlers/register.rs @@ -13,13 +13,25 @@ pub async fn get_register( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let register_id = user_repo::get_register_id().await?; + let iota_id = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| user_repo::valid_protocol_id(*id)); + let Some(iota_id) = iota_id else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let (register_id, registration_token) = + user_repo::allocate_registration(IotaId::from(iota_id), value.get_id()).await?; let response = CommunicationValue::new(CommunicationType::GetRegister) .with_id(value.get_id()) .add_typed_default( DataType::UserId, DataValue::SignedNumber(register_id.0.into()), - ); + ) + .add_typed_default(DataType::RegisterId, DataValue::Str(registration_token)); connection.send(&response).await } @@ -27,10 +39,6 @@ pub async fn complete_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let iota_id = value - .get_data(DataType::IotaId) - .as_number() - .map(|id| id as i64); let public_key = value .get_data(DataType::PublicKey) .as_str() @@ -40,57 +48,25 @@ pub async fn complete_iota( .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; }; - match iota_id { - Some(iota_id) => { - match iota_repo::register_complete_iota(IotaId::from(iota_id), public_key).await { - Ok(()) => { - connection - .send( - &CommunicationValue::new(CommunicationType::Success) - .with_id(value.get_id()), - ) - .await - } - Err(error) => { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error.to_string()), - ), - ) - .await - } - } + match iota_repo::create_new_iota(public_key).await { + Ok(id) => { + connection + .send( + &CommunicationValue::new(CommunicationType::CompleteRegisterIota) + .with_id(value.get_id()) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.0.into())), + ) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await } - None => match iota_repo::create_new_iota(public_key).await { - Ok(id) => { - connection - .send( - &CommunicationValue::new(CommunicationType::CompleteRegisterIota) - .with_id(value.get_id()) - .add_typed_default( - DataType::IotaId, - DataValue::SignedNumber(id.0.into()), - ), - ) - .await - } - Err(error) => { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str(error.to_string()), - ), - ) - .await - } - }, } } @@ -101,7 +77,8 @@ pub async fn complete_user( let user_id = value .get_data(DataType::UserId) .as_number() - .map(|id| id as i64); + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| user_repo::valid_protocol_id(*id)); let username = value .get_data(DataType::Username) .as_str() @@ -114,22 +91,44 @@ pub async fn complete_user( .get_data(DataType::ResetToken) .as_str() .map(str::to_owned); - let Some((user_id, username, public_key, reset_token)) = user_id + let registration_token = value + .get_data(DataType::RegisterId) + .as_str() + .filter(|token| uuid::Uuid::parse_str(token).is_ok()) + .map(str::to_owned); + let Some((user_id, username, public_key, reset_token, registration_token)) = user_id .zip(username) .zip(public_key) .zip(reset_token) - .map(|(((id, name), key), token)| (id, name, key, token)) + .zip(registration_token) + .map(|((((id, name), key), token), registration_token)| { + (id, name, key, token, registration_token) + }) else { return connection .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; }; + // Omikron supplies the authenticated Iota ID in the payload. The lease + // check below binds completion to that Iota rather than trusting sender. + let iota_id = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| user_repo::valid_protocol_id(*id)); + let Some(iota_id) = iota_id else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + match user_repo::register_complete_user( UserId::from(user_id), username, public_key, - IotaId::from(value.get_sender() as i64), + IotaId::from(iota_id), reset_token, + registration_token, ) .await { diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 7095f9d..d48120c 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -91,10 +91,29 @@ impl OmikronConnection { .retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE); } })); - while let Ok(value) = receiver.receive().await { - if let Err(error) = self.clone().process_message(value).await { - log_err!(0, PrintType::Omega, "Error processing message: {}", error); - if matches!(error, crate::error::OmegaError::NotConnected) { + loop { + match receiver.receive().await { + Ok(value) => { + if let Err(error) = self.clone().process_message(value).await { + log_err!( + self.id as i64, + PrintType::Omega, + "Error processing Omikron message: {}", + error + ); + if matches!(error, crate::error::OmegaError::NotConnected) { + break; + } + } + } + Err(error) => { + log_err!( + self.id as i64, + PrintType::Omega, + "Omikron receive loop ended: {}; transport close reason: {:?}", + error, + receiver.close_reason() + ); break; } } @@ -133,6 +152,9 @@ impl OmikronConnection { Some(CommunicationType::UserDisconnected) => { crate::transport::handlers::presence::user_disconnected(self, value, id).await } + Some(CommunicationType::ClientChanged) => { + crate::transport::handlers::presence::client_changed(self, value, id).await + } Some(CommunicationType::IotaConnected) => { crate::transport::handlers::presence::iota_connected(self, value, id).await } @@ -259,40 +281,38 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { pub async fn start(port: u16) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "cert.pem")?; let key_pem = load_file_vec("certs", "key.pem")?; - let web_config = server::server::build_web_config()?; - let host_config = HostConfig::new( - IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), - port, - cert_pem, - key_pem, - ) - .with_policy(Policy { - send_mode: SendMode::SingleStreamPerMessage, - max_message_size: 1_000_000_000, - handshake_max_message_size: 1_000_000, - close_frame_len: u32::MAX, - application_close_code: 0, - open_stream_timeout: Duration::from_millis(2_000), - write_timeout: Duration::from_millis(2_000), - accept_stream_timeout: Duration::from_millis(10_000), - read_timeout: Duration::from_millis(30_000), - keep_alive_interval: Some(Duration::from_secs(6)), - max_idle_timeout: Some(Duration::from_secs(30)), - force_close_delay: Duration::from_millis(300), - receiver_queue_capacity: 1000, - max_concurrent_stream_tasks: 10, - persistent_stream_max_retries: 5, - persistent_stream_retry_backoff: Duration::from_secs(5), - max_frames_per_stream: None, - }) - .with_authentication( - load_keyring(), - Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))), - Box::new(|key, description| Box::pin(complete_register(key, description))), - ) - .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); + let web_config = server::server::build_web_config()? + .serve_tcp_https(true) + .max_tcp_connections(256); + let ip = IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)); + let host_config = HostConfig::new(ip, port, cert_pem, key_pem) + .with_policy(Policy { + send_mode: SendMode::SingleStreamPerMessage, + max_message_size: 1_000_000_000, + handshake_max_message_size: 1_000_000, + close_frame_len: u32::MAX, + application_close_code: 0, + open_stream_timeout: Duration::from_millis(5_000), + write_timeout: Duration::from_millis(5_000), + accept_stream_timeout: Duration::from_millis(10_000), + read_timeout: Duration::from_millis(30_000), + keep_alive_interval: Some(Duration::from_secs(6)), + max_idle_timeout: Some(Duration::from_secs(30)), + force_close_delay: Duration::from_millis(300), + receiver_queue_capacity: 1000, + max_concurrent_stream_tasks: 64, + persistent_stream_max_retries: 5, + persistent_stream_retry_backoff: Duration::from_secs(5), + max_frames_per_stream: None, + }) + .with_authentication( + load_keyring(), + Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))), + Box::new(|key, description| Box::pin(complete_register(key, description))), + ) + .with_authentication_policy(AuthenticationPolicy::ForceAuthentication); let mut server = MTPWebServer::new(host_config, web_config).await?; - log!("OmegaServer listening on port {}", port); + log!("OmegaServer listening on {}:{}", ip.to_string(), port); loop { let mut conn = match server.accept().await { Ok(Some(conn)) => conn, diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 2de4ea3..0b2f66c 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -26,12 +26,18 @@ pub async fn remove_omikron(omikron_id: i64) { OMIKRON_CONNECTIONS.remove(&omikron_id); } +pub fn get_connected_omikron(omikron_id: i64) -> Option> { + OMIKRON_CONNECTIONS + .get(&omikron_id) + .map(|connection| connection.clone()) +} + pub async fn get_random_omikron() -> Result, ()> { let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); if let Some(key) = keys.into_iter().choose(&mut rand::rng()) { - if let Some(entry) = OMIKRON_CONNECTIONS.get(&key) { - return Ok(entry.clone()); + if let Some(connection) = get_connected_omikron(key) { + return Ok(connection); } } diff --git a/src/util/logger.rs b/src/util/logger.rs index 39eb933..4276cc0 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -244,8 +244,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String { parts.push(format!("> {}", receiver)); } - let comm_type = cv.get_type().to_string(); - parts.push(format!("{}", comm_type)); + let comm_type = cv + .get_comm_type_enum() + .map(|kind| kind.to_string()) + .unwrap_or_else(|| cv.get_type().to_string()); + parts.push(format!("{} (id={})", comm_type, cv.get_id())); let data = cv.data(); diff --git a/type-maps.yaml b/type-maps.yaml deleted file mode 100644 index ce3d5e6..0000000 --- a/type-maps.yaml +++ /dev/null @@ -1,269 +0,0 @@ -protocol_version: "1.0" - -# Note that markers 0 to 31 are reserved for default use, manually working with them is not recommended - -# Fixed CommunicationType markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorBadVersion: 2 -# Disconnect: 3 -# Redirect: 4 -# Shutdown: 5 -# BadRequest: 6 -# Unauthorized: 7 -# Forbidden: 8 -# NotFound: 9 -# TooManyRequests: 10 -# InternalServerError: 11 -# BadGateway: 12 -# ServiceUnavailable: 13 -# GatewayTimeout: 14 -# Identification: 15 -# IdentificationResponse: 16 -# Register: 17 -# RegisterResponse: 18 -# Ping: 19 -# Pong: 20 - -# Fixed Data Type markers are: -# Error: 0 -# ErrorParsing: 1 -# ErrorMessage: 2 -# Version: 3 -# Description: 4 -# Timestamp: 5 -# Id: 6 -# ClientNonce: 7 -# ServerNonce: 8 -# PublicKeys: 9 -# Signature: 10 -# Connected: 11 - -type_maps: - "1.0": - CommunicationTypes: - ErrorProtocol: 33 - ErrorAnonymous: 34 - ErrorInternal: 35 - ErrorInvalidData: 36 - ErrorInvalidUserId: 37 - ErrorInvalidOmikronId: 38 - ErrorNotFound: 39 - ErrorNotAuthenticated: 40 - ErrorNoIota: 41 - ErrorInvalidChallenge: 42 - ErrorInvalidSecret: 43 - ErrorInvalidPrivateKey: 44 - ErrorInvalidPublicKey: 45 - ErrorNoUserId: 46 - ErrorNoCallId: 47 - ErrorInvalidCallId: 48 - Success: 49 - ShortenLink: 50 - SettingsSave: 51 - SettingsLoad: 52 - SettingsList: 53 - GlobalSettingsSave: 54 - GlobalSettingsLoad: 55 - Message: 56 - MessageState: 57 - MessageSend: 58 - MessageLive: 59 - MessageOtherIota: 60 - MessageChunk: 61 - MessageGet: 143 - MessagesGet: 62 - PushNotification: 63 - ReadNotification: 64 - GetNotifications: 65 - TauriIdentification: 66 - ChangeConfirm: 67 - ConfirmReceive: 68 - ConfirmRead: 69 - GetChats: 70 - GetStates: 71 - AddCommunity: 72 - RemoveCommunity: 73 - GetCommunities: 74 - RegisterIota: 81 - RegisterIotaSuccess: 82 - AddConversation: 85 - SendChat: 86 - ClientChanged: 87 - ClientConnected: 88 - ClientDisconnected: 89 - ClientClosed: 90 - PublicKey: 91 - PrivateKey: 92 - WebrtcSdp: 93 - WebrtcIce: 94 - StartStream: 95 - EndStream: 96 - WatchStream: 97 - CallToken: 98 - CallInvite: 99 - CallDisconnectUser: 100 - CallTimeoutUser: 101 - CallSetAnonymousJoining: 102 - CallData: 103 - EndCall: 104 - Function: 105 - Update: 106 - CreateUser: 107 - RhoUpdate: 108 - UserConnected: 109 - UserDisconnected: 110 - IotaConnected: 111 - IotaDisconnected: 112 - SyncClientIotaStatus: 113 - GetUserData: 114 - GetIotaData: 115 - IotaUserData: 116 - ChangeUserData: 117 - ChangeIotaData: 118 - GetRegister: 119 - CompleteRegisterUser: 120 - CompleteRegisterIota: 121 - DeleteUser: 122 - DeleteIota: 123 - StartRegister: 124 - CompleteRegister: 125 - GetApp: 126 - CreateApp: 127 - DeleteApp: 128 - SaveAppData: 129 - LoadAppData: 130 - AppIdentification: 131 - AppChallenge: 132 - AppChallengeResponse: 133 - AppIdentificationResponse: 134 - LoadTxtRecord: 135 - ErrorNotSet: 136 - SetChatSecret: 139 - GetChatSecret: 140 - ChatSecretResponse: 141 - ChatSecretForward: 142 - MessageEditLive: 144 - MessageEdit: 145 - MessageReactionAdd: 146 - MessageReactionRemove: 147 - MessageReactionLive: 148 - MessageDeleteLive: 150 - DataTypes: - ErrorType: 32 - ErrorProtocol: 33 - AcceptedIds: 34 - Uuid: 35 - RegisterId: 36 - Link: 37 - Settings: 38 - SettingsName: 39 - ChatPartnerId: 40 - ChatPartnerName: 41 - IotaId: 42 - UserId: 43 - UserIds: 44 - IotaIds: 45 - UserState: 46 - UserStates: 47 - UserPings: 48 - CallState: 49 - ScreenShare: 50 - PrivateKeyHash: 51 - # Accepted: 52 now part of default MTP - AcceptedProfiles: 53 - DeniedProfiles: 54 - Content: 55 - Messages: 56 - Notifications: 57 - SendTime: 58 - GetTime: 59 - GetVariant: 60 - SharedSecretOwn: 61 - SharedSecretOther: 62 - SharedSecretSign: 63 - SharedSecret: 64 - CallId: 65 - CallToken: 66 - CallSecret: 67 - Untill: 68 - Enabled: 69 - StartDate: 70 - EndDate: 71 - ReceiverId: 72 - SenderId: 73 - Signed: 75 - Message: 76 - MessageState: 77 - LastPing: 78 - PingIota: 79 - PingClients: 80 - Matches: 81 - Omikron: 82 - Offset: 83 - Amount: 84 - Position: 85 - Name: 86 - Path: 87 - Codec: 88 - Function: 89 - Payload: 90 - Result: 91 - Interactables: 92 - WantToWatch: 93 - Watcher: 94 - CreatedAt: 95 - Username: 96 - Display: 97 - Avatar: 98 - About: 99 - Status: 100 - PublicKey: 101 - SubLevel: 102 - SubEnd: 103 - CommunityAddress: 104 - CommunityTitle: 106 - Communities: 107 - RhoConnections: 108 - User: 109 - OnlineStatus: 110 - OmikronId: 111 - OmikronConnections: 112 - ResetToken: 113 - NewToken: 114 - CallInvited: 115 - CallMembers: 116 - Calls: 117 - Timeout: 118 - HasAdmin: 119 - LastMessageAt: 120 - Height: 121 - SentBySelf: 122 - SessionId: 123 - Contacts: 124 - LastMessage: 125 - AppIdentifier: 127 - AppPrivateKey: 128 - AppPublicKey: 129 - AppSession: 130 - AppData: 131 - TauriToken: 132 - Challenge: 133 - EncryptedPayload: 134 - SecurePayload: 135 - DeviceId: 136 - ClientId: 137 - SecretId: 142 - VersionNumber: 143 - EncryptedSecret: 144 - WrappingScheme: 146 - UpdatedAt: 147 - ChatId: 148 - KemCiphertext: 149 - SenderUserId: 152 - RecipientUserId: 153 - Recipients: 154 - Edited: 155 - Reactions: 156 - Reaction: 157 - ReplyId: 158 From 4c56adf6ba5018beeded1ac345c820ed5a58dbb1 Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 27 Jul 2026 22:54:31 +0200 Subject: [PATCH 191/220] (feat): update mtp --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c1774a..ee52b1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "async-trait", "bytes", @@ -3515,7 +3515,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From b94dbd700655309304a59b45b8f8b7f97d8d0321 Mon Sep 17 00:00:00 2001 From: Alois Date: Mon, 27 Jul 2026 22:54:31 +0200 Subject: [PATCH 192/220] (feat): update mtp --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c1774a..ee52b1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#88ae866b91857c9b1650ea0ee2af2fdb70b92f10" +source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" dependencies = [ "async-trait", "bytes", @@ -3515,7 +3515,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From 02bd4c0132841e3a93851e21c8fed0b9367b56b7 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 01:02:05 +0200 Subject: [PATCH 193/220] (fix): fix connection issues --- src/transport/omikron_connection.rs | 10 +++++++--- src/transport/omikron_manager.rs | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index d48120c..54a3bf9 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -245,16 +245,20 @@ impl OmikronConnection { } pub async fn close(self: Arc) { log_in!( - self.get_omikron_id().await.unwrap_or(0), + self.id as i64, PrintType::Omega, "Omikron connection Closed" ); + if let Some(sender) = self.sender.lock().await.take() { + sender.close(); + } } async fn cleanup(self: Arc) { if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); - omikron_manager::remove_omikron(self.id as i64).await; - crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; + if omikron_manager::remove_omikron(self.id as i64, &self).await { + crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; + } } if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 0b2f66c..0e1575d 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -22,8 +22,10 @@ pub async fn add_omikron(conn: Arc) { } } -pub async fn remove_omikron(omikron_id: i64) { - OMIKRON_CONNECTIONS.remove(&omikron_id); +pub async fn remove_omikron(omikron_id: i64, connection: &Arc) -> bool { + OMIKRON_CONNECTIONS + .remove_if(&omikron_id, |_, current| Arc::ptr_eq(current, connection)) + .is_some() } pub fn get_connected_omikron(omikron_id: i64) -> Option> { From be0cad295d7eb556c2280e1042232f6dece6cbb1 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 01:02:05 +0200 Subject: [PATCH 194/220] (fix): fix connection issues --- src/transport/omikron_connection.rs | 10 +++++++--- src/transport/omikron_manager.rs | 6 ++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index d48120c..54a3bf9 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -245,16 +245,20 @@ impl OmikronConnection { } pub async fn close(self: Arc) { log_in!( - self.get_omikron_id().await.unwrap_or(0), + self.id as i64, PrintType::Omega, "Omikron connection Closed" ); + if let Some(sender) = self.sender.lock().await.take() { + sender.close(); + } } async fn cleanup(self: Arc) { if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); - omikron_manager::remove_omikron(self.id as i64).await; - crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; + if omikron_manager::remove_omikron(self.id as i64, &self).await { + crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; + } } if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 0b2f66c..0e1575d 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -22,8 +22,10 @@ pub async fn add_omikron(conn: Arc) { } } -pub async fn remove_omikron(omikron_id: i64) { - OMIKRON_CONNECTIONS.remove(&omikron_id); +pub async fn remove_omikron(omikron_id: i64, connection: &Arc) -> bool { + OMIKRON_CONNECTIONS + .remove_if(&omikron_id, |_, current| Arc::ptr_eq(current, connection)) + .is_some() } pub fn get_connected_omikron(omikron_id: i64) -> Option> { From 2f0762fd44a88bcdd72eea9c3ed3845a21704eb9 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 12:44:46 +0200 Subject: [PATCH 195/220] (fix): connections --- Cargo.lock | 22 +++++++++++----------- src/transport/omikron_connection.rs | 27 ++++----------------------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee52b1c..a5b9c46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "async-trait", "bytes", @@ -3515,7 +3515,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 54a3bf9..e86f4a2 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -6,7 +6,7 @@ use crate::{ }; use dashmap::DashMap; use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + codec::{CommunicationType, CommunicationValue}, crypto::PublicKeyBundle, host::{AuthenticationPolicy, HostConfig, Policy, SendMode}, webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}, @@ -19,10 +19,7 @@ use std::{ }, time::{Duration, Instant}, }; -use tokio::{ - sync::{Mutex, RwLock}, - time::interval, -}; +use tokio::{sync::Mutex, time::interval}; const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); @@ -52,7 +49,6 @@ pub struct WaitingTask { pub struct OmikronConnection { id: u64, sender: Mutex>, - pub ping: RwLock, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, } @@ -69,7 +65,6 @@ impl OmikronConnection { Arc::new(Self { id, sender: Mutex::new(Some(sender)), - ping: RwLock::new(-1), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), }) @@ -127,16 +122,11 @@ impl OmikronConnection { } async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { - if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { - log_cv_in!(PrintType::Omikron, &value); - } + log_cv_in!(PrintType::Omikron, &value); if let Some((_, task)) = self.waiting_tasks.remove(&value.get_id()) { let _ = (task.task)(self.clone(), value); return Ok(()); } - if value.is_type(CommunicationType::Ping) { - return self.ping(value).await; - } self.dispatch(value).await } @@ -215,17 +205,8 @@ impl OmikronConnection { } } - async fn ping(self: Arc, value: CommunicationValue) -> OmikronResult<()> { - if let DataValue::SignedNumber(last_ping) = value.get_data(DataType::LastPing) { - *self.ping.write().await = *last_ping as i64; - } - let response = CommunicationValue::new(CommunicationType::Pong).with_id(value.get_id()); - self.send(&response).await - } pub(crate) async fn send(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { - if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { - log_cv_out!(PrintType::Omikron, value); - } + log_cv_out!(PrintType::Omikron, value); let guard = self.sender.lock().await; let sender = guard .as_ref() From 6658e02eed458ff024352fd6e0b2ebf92cda57a0 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 12:44:46 +0200 Subject: [PATCH 196/220] (fix): connections --- Cargo.lock | 22 +++++++++++----------- src/transport/omikron_connection.rs | 27 ++++----------------------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ee52b1c..a5b9c46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#bcf8aee3716f1690f1284748ac1ff3cd22799fb0" +source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" dependencies = [ "async-trait", "bytes", @@ -3515,7 +3515,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 54a3bf9..e86f4a2 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -6,7 +6,7 @@ use crate::{ }; use dashmap::DashMap; use mtp::{ - codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + codec::{CommunicationType, CommunicationValue}, crypto::PublicKeyBundle, host::{AuthenticationPolicy, HostConfig, Policy, SendMode}, webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}, @@ -19,10 +19,7 @@ use std::{ }, time::{Duration, Instant}, }; -use tokio::{ - sync::{Mutex, RwLock}, - time::interval, -}; +use tokio::{sync::Mutex, time::interval}; const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); @@ -52,7 +49,6 @@ pub struct WaitingTask { pub struct OmikronConnection { id: u64, sender: Mutex>, - pub ping: RwLock, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, } @@ -69,7 +65,6 @@ impl OmikronConnection { Arc::new(Self { id, sender: Mutex::new(Some(sender)), - ping: RwLock::new(-1), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), }) @@ -127,16 +122,11 @@ impl OmikronConnection { } async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { - if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { - log_cv_in!(PrintType::Omikron, &value); - } + log_cv_in!(PrintType::Omikron, &value); if let Some((_, task)) = self.waiting_tasks.remove(&value.get_id()) { let _ = (task.task)(self.clone(), value); return Ok(()); } - if value.is_type(CommunicationType::Ping) { - return self.ping(value).await; - } self.dispatch(value).await } @@ -215,17 +205,8 @@ impl OmikronConnection { } } - async fn ping(self: Arc, value: CommunicationValue) -> OmikronResult<()> { - if let DataValue::SignedNumber(last_ping) = value.get_data(DataType::LastPing) { - *self.ping.write().await = *last_ping as i64; - } - let response = CommunicationValue::new(CommunicationType::Pong).with_id(value.get_id()); - self.send(&response).await - } pub(crate) async fn send(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { - if !value.is_type(CommunicationType::Pong) && !value.is_type(CommunicationType::Ping) { - log_cv_out!(PrintType::Omikron, value); - } + log_cv_out!(PrintType::Omikron, value); let guard = self.sender.lock().await; let sender = guard .as_ref() From 2fa02342bee7b872502b03f913f0e3e5f66e027b Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 12:44:57 +0200 Subject: [PATCH 197/220] (fix): connections --- mtp-type-maps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mtp-type-maps b/mtp-type-maps index 594646a..ece6e2c 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit 594646ac39d986f0787aa614a99d580035a67318 +Subproject commit ece6e2c3b4e925f3cefe46f4a048fbfc8f823093 From 3544a3d7058c739538aa461859943287ad52c782 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 12:44:57 +0200 Subject: [PATCH 198/220] (fix): connections --- mtp-type-maps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mtp-type-maps b/mtp-type-maps index 594646a..ece6e2c 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit 594646ac39d986f0787aa614a99d580035a67318 +Subproject commit ece6e2c3b4e925f3cefe46f4a048fbfc8f823093 From 0edce11f1d1159e7f6d38f1ad503c40506da0d72 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 21:05:31 +0200 Subject: [PATCH 199/220] (feat): update mtp --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5b9c46..c4efa2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,13 +579,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -1460,7 +1460,7 @@ dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.9.0", + "redox_syscall 0.9.1", ] [[package]] @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "async-trait", "bytes", @@ -2224,9 +2224,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" dependencies = [ "bitflags", ] From 20b235fc21d75f75ff49da2998652931e1c9fd78 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 21:05:31 +0200 Subject: [PATCH 200/220] (feat): update mtp --- Cargo.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a5b9c46..c4efa2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -579,13 +579,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -1460,7 +1460,7 @@ dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.9.0", + "redox_syscall 0.9.1", ] [[package]] @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#fa271e62bec7918a8c994eea59ecdd62be76ead4" +source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" dependencies = [ "async-trait", "bytes", @@ -2224,9 +2224,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" dependencies = [ "bitflags", ] From eb0f401c2e1a5ba35629900b747efd2852792121 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 23:53:33 +0200 Subject: [PATCH 201/220] (fix): connections (again) --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4efa2a..b6f6c7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "async-trait", "bytes", From 970e657d3764affd0b621b5acda88fae5e7822f4 Mon Sep 17 00:00:00 2001 From: Alois Date: Tue, 28 Jul 2026 23:53:33 +0200 Subject: [PATCH 202/220] (fix): connections (again) --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c4efa2a..b6f6c7c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#590810ce59709efccd81ca04401c855be314b9c0" +source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" dependencies = [ "async-trait", "bytes", From d863089daa1e84379ef292b354ee97f0bac44a8d Mon Sep 17 00:00:00 2001 From: Alois Date: Wed, 29 Jul 2026 02:37:18 +0200 Subject: [PATCH 203/220] (feat): update mtp --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6f6c7c..34e4f21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "async-trait", "bytes", @@ -3515,7 +3515,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From 84a45c4151a438821367b4cd0fb64123b839ada1 Mon Sep 17 00:00:00 2001 From: Alois Date: Wed, 29 Jul 2026 02:37:18 +0200 Subject: [PATCH 204/220] (feat): update mtp --- Cargo.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6f6c7c..34e4f21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1614,7 +1614,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1630,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "base64", "byteorder", @@ -1656,7 +1656,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "quinn", "rustls", @@ -1667,7 +1667,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "base64", "chacha20poly1305", @@ -1689,7 +1689,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1700,7 +1700,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "mtp-codec", "mtp-common", @@ -1715,7 +1715,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1733,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#6f673ba7f2834b95ce724babe131241df8a119c6" +source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" dependencies = [ "async-trait", "bytes", @@ -3515,7 +3515,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.48.0", ] [[package]] From 017b69557813bf8b4abbda9c2d2ecd266a9e147f Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Wed, 5 Aug 2026 20:15:15 +0200 Subject: [PATCH 205/220] Add renovate.json --- renovate.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..7190a60 --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json" +} From 366907ba7aed0c157345a2cb35733bc142c8c708 Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Wed, 5 Aug 2026 20:15:15 +0200 Subject: [PATCH 206/220] Add renovate.json --- renovate.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..7190a60 --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json" +} From 0de811544e4766a3faa9b72091b435103fe3845e Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Wed, 5 Aug 2026 22:01:11 +0200 Subject: [PATCH 207/220] Update Rust crate http to v1.5.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34e4f21..626eeaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1077,9 +1077,9 @@ checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", From 567982788ad704ab92ca750f57fe4648a39a18c4 Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Wed, 5 Aug 2026 22:01:11 +0200 Subject: [PATCH 208/220] Update Rust crate http to v1.5.0 --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34e4f21..626eeaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1077,9 +1077,9 @@ checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", From b65b22dfbc322487226f8a3c0480759b6f3ed7b9 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 7 Aug 2026 23:54:34 +0200 Subject: [PATCH 209/220] [Fix] User States --- migrations/004_presence_preference.sql | 22 + mtp-type-maps | 2 +- src/db/user_repo.rs | 143 ++- src/main.rs | 4 +- src/models/user.rs | 1 + src/server/api.rs | 6 +- src/sql/connection_status.rs | 75 +- src/sql/user_online_tracker.rs | 1277 ++++++++++++++++++++---- src/state.rs | 72 ++ src/transport/capabilities.rs | 147 +++ src/transport/handlers/presence.rs | 862 ++++++++++++++-- src/transport/handlers/states.rs | 156 ++- src/transport/handlers/user_data.rs | 51 +- src/transport/mod.rs | 1 + src/transport/omikron_connection.rs | 129 ++- src/transport/omikron_manager.rs | 66 +- 16 files changed, 2613 insertions(+), 401 deletions(-) create mode 100644 migrations/004_presence_preference.sql create mode 100644 src/state.rs create mode 100644 src/transport/capabilities.rs diff --git a/migrations/004_presence_preference.sql b/migrations/004_presence_preference.sql new file mode 100644 index 0000000..163a623 --- /dev/null +++ b/migrations/004_presence_preference.sql @@ -0,0 +1,22 @@ +ALTER TABLE users + ADD COLUMN presence_preference VARBINARY(32) NOT NULL DEFAULT 'user_online'; + +UPDATE users +SET presence_preference = 'user_online' +WHERE presence_preference NOT IN ( + 'user_online', + 'user_idle', + 'user_dnd', + 'user_wc', + 'user_invisible' +); + +ALTER TABLE users + ADD CONSTRAINT chk_users_presence_preference + CHECK (presence_preference IN ( + 'user_online', + 'user_idle', + 'user_dnd', + 'user_wc', + 'user_invisible' + )); diff --git a/mtp-type-maps b/mtp-type-maps index ece6e2c..486541b 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit ece6e2c3b4e925f3cefe46f4a048fbfc8f823093 +Subproject commit 486541b9483356ff49ff3ec7016f87d3ecbeaa0e diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index 8554566..01b0e6e 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -2,9 +2,11 @@ use crate::{ db::pool, error::{OmegaError, Result}, models::{IotaId, User, UserId}, + sql::connection_status::UserStatus, }; use mtp::crypto::PublicKeyBundle; -use sqlx::{FromRow, Row}; +use sqlx::{FromRow, MySql, QueryBuilder, Row}; +use std::collections::HashMap; pub const MAX_PROTOCOL_ID: i64 = (1_i64 << 48) - 1; const ID_ALLOCATION_ATTEMPTS: usize = 16; @@ -92,9 +94,9 @@ pub(crate) fn is_duplicate_key(error: &sqlx::Error) -> bool { }) } -const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; -const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; -const USERS_BY_IOTA_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE iota_id = ?"; +const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, presence_preference, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; +const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, presence_preference, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; +const USER_COLUMNS: &str = "SELECT id, iota_id, username, display, status, presence_preference, about, avatar, sub_level, sub_end, public_key, token FROM users"; #[derive(FromRow)] struct UserRow { @@ -103,6 +105,7 @@ struct UserRow { username: Vec, display: Option>, status: Option>, + presence_preference: Vec, about: Option>, avatar: Option>, sub_level: i32, @@ -125,6 +128,7 @@ impl TryFrom for User { username: decode(row.username)?, display: row.display.map(decode).transpose()?, status: row.status.map(decode).transpose()?, + presence_preference: decode(row.presence_preference)?, about: row.about.map(decode).transpose()?, avatar: row.avatar, sub_level: row.sub_level, @@ -154,15 +158,129 @@ pub async fn get_by_user_id(id: UserId) -> Result { } pub async fn get_users_by_iota_id(id: IotaId) -> Result> { - let rows = sqlx::query_as::<_, UserRow>(USERS_BY_IOTA_ID_QUERY) - .bind(id.0) + get_users_by_iota_ids(&[id.0]).await +} + +fn normalized_ids(ids: &[i64]) -> Vec { + let mut ids = ids + .iter() + .copied() + .filter(|id| valid_protocol_id(*id)) + .collect::>(); + ids.sort_unstable(); + ids.dedup(); + ids +} + +fn append_in_clause(query: &mut QueryBuilder<'_, MySql>, ids: &[i64]) { + query.push("("); + for (index, id) in ids.iter().enumerate() { + if index > 0 { + query.push(", "); + } + query.push_bind(*id); + } + query.push(")"); +} + +async fn fetch_users(mut query: QueryBuilder<'_, MySql>) -> Result> { + query + .build_query_as::() .fetch_all(&pool().await?) - .await?; - rows.into_iter() + .await? + .into_iter() .map(|row| row.try_into().map_err(OmegaError::from)) .collect() } +pub async fn get_users_by_ids(ids: &[i64]) -> Result> { + let ids = normalized_ids(ids); + if ids.is_empty() { + return Ok(Vec::new()); + } + let mut query = QueryBuilder::::new(USER_COLUMNS); + query.push(" WHERE id IN "); + append_in_clause(&mut query, &ids); + fetch_users(query).await +} + +pub async fn get_users_by_iota_ids(ids: &[i64]) -> Result> { + let ids = normalized_ids(ids); + if ids.is_empty() { + return Ok(Vec::new()); + } + let mut query = QueryBuilder::::new(USER_COLUMNS); + query.push(" WHERE iota_id IN "); + append_in_clause(&mut query, &ids); + fetch_users(query).await +} + +pub async fn get_users_by_ids_and_iota_ids( + user_ids: &[i64], + iota_ids: &[i64], +) -> Result> { + let user_ids = normalized_ids(user_ids); + let iota_ids = normalized_ids(iota_ids); + if user_ids.is_empty() && iota_ids.is_empty() { + return Ok(Vec::new()); + } + + let mut query = QueryBuilder::::new(USER_COLUMNS); + query.push(" WHERE "); + if !user_ids.is_empty() { + query.push("id IN "); + append_in_clause(&mut query, &user_ids); + } + if !iota_ids.is_empty() { + if !user_ids.is_empty() { + query.push(" OR "); + } + query.push("iota_id IN "); + append_in_clause(&mut query, &iota_ids); + } + fetch_users(query).await +} + +#[derive(FromRow)] +struct PresencePreferenceRow { + id: i64, + presence_preference: Vec, +} + +pub async fn get_presence_preferences(ids: &[i64]) -> Result> { + let ids = normalized_ids(ids); + if ids.is_empty() { + return Ok(HashMap::new()); + } + + let mut query = + QueryBuilder::::new("SELECT id, presence_preference FROM users WHERE id IN "); + append_in_clause(&mut query, &ids); + let rows = query + .build_query_as::() + .fetch_all(&pool().await?) + .await?; + let mut preferences = HashMap::with_capacity(rows.len()); + for row in rows { + let status = String::from_utf8(row.presence_preference) + .ok() + .and_then(|value| UserStatus::from_client_preference(&value)); + let status = match status { + Some(status) => status, + None => { + crate::log_in!( + crate::util::logger::PrintType::General, + "Invalid persisted presence preference for user {}, using user_online", + row.id + ); + UserStatus::user_online + } + }; + preferences.insert(row.id, status); + } + Ok(preferences) +} + async fn update( id: UserId, query: &'static str, @@ -217,6 +335,15 @@ pub async fn change_status(id: UserId, value: String) -> Result<()> { .await } +pub async fn change_presence_preference(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET presence_preference = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} + pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> { sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") .bind(value.0) diff --git a/src/main.rs b/src/main.rs index 251713e..16b850e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,12 +5,14 @@ pub mod error; mod models; mod server; mod sql; +mod state; mod transport; mod util; pub use error::{OmegaError, Result}; use crate::db::initialize; +use crate::state::OmegaState; use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; @@ -94,7 +96,7 @@ async fn main() { .unwrap_or(443); tokio::select! { - result = omikron_connection::start(port) => { + result = omikron_connection::start(port, OmegaState::new()) => { if let Err(e) = result { log_err!(0, PrintType::General, "Server error: {:?}", e); } diff --git a/src/models/user.rs b/src/models/user.rs index 0dda84c..2843b6d 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -8,6 +8,7 @@ pub struct User { pub username: String, pub display: Option, pub status: Option, + pub presence_preference: String, pub about: Option, pub avatar: Option>, pub sub_level: i32, diff --git a/src/server/api.rs b/src/server/api.rs index ed5bb80..498685a 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -14,8 +14,10 @@ use crate::server::{ middleware, validation::{parse_positive_id, validate_non_empty}, }; -use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; -use crate::transport::omikron_manager::{get_connected_omikron, get_random_omikron}; +use crate::transport::omikron_manager::{ + get_all_connections, get_connected_omikron, get_iota_primary_omikron_connection, + get_random_omikron, +}; use crate::util::file_util::get_directory; use base64::Engine as _; use bytes::Bytes; diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 8587ffe..104e316 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -1,7 +1,4 @@ -use strum::IntoEnumIterator; -use strum_macros::EnumIter; - -#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(unused, non_camel_case_types)] pub enum UserStatus { user_offline, @@ -20,12 +17,70 @@ impl UserStatus { pub fn to_string(&self) -> String { format!("{:?}", self) } - pub fn from_str(s: &str) -> Option { - for sel in UserStatus::iter() { - if &sel.to_string() == s { - return Some(sel); - } + pub fn from_client_preference(s: &str) -> Option { + match s { + "user_online" => Some(Self::user_online), + "user_idle" => Some(Self::user_idle), + "user_dnd" => Some(Self::user_dnd), + "user_wc" => Some(Self::user_wc), + "user_invisible" => Some(Self::user_invisible), + _ => None, } - None + } + + pub fn public_value(&self) -> Self { + match self { + Self::user_invisible => Self::user_offline, + value => value.clone(), + } + } + + /// Parse a value received from a client or persisted as an account + /// preference. Derived connectivity and diagnostic states are never valid + /// preferences. + pub fn from_str(s: &str) -> Option { + Self::from_client_preference(s) + } +} + +#[cfg(test)] +mod tests { + use super::UserStatus; + + #[test] + fn accepts_only_client_preferences() { + for value in [ + "user_online", + "user_idle", + "user_dnd", + "user_wc", + "user_invisible", + ] { + assert!( + UserStatus::from_client_preference(value).is_some(), + "{value}" + ); + } + + for value in [ + "user_offline", + "iota_offline", + "iota_online", + "user_borked", + "iota_borked", + "unknown", + ] { + assert_eq!(UserStatus::from_client_preference(value), None, "{value}"); + assert_eq!(UserStatus::from_str(value), None, "{value}"); + } + } + + #[test] + fn invisible_is_publicly_offline() { + assert_eq!( + UserStatus::user_invisible.public_value(), + UserStatus::user_offline + ); + assert_eq!(UserStatus::user_dnd.public_value(), UserStatus::user_dnd); } } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index b0e5f91..8017c3d 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,231 +1,1094 @@ -use crate::db::user_repo; -use crate::models::IotaId; use crate::sql::connection_status::UserStatus; -use dashmap::DashMap; -use once_cell::sync::Lazy; +use std::collections::{HashMap, HashSet}; +use std::sync::RwLock; -#[derive(Debug, Clone)] -pub struct UserConnection { - pub connection_type: UserStatus, +fn select_primary_route(previous_primary: Option, connected: &HashSet) -> Option { + previous_primary + .filter(|id| connected.contains(id)) + .or_else(|| connected.iter().min().copied()) +} + +fn sorted_vec(values: HashSet) -> Vec { + let mut values = values.into_iter().collect::>(); + values.sort_unstable(); + values +} + +fn sorted_pairs(values: HashSet<(i64, i64)>) -> Vec<(i64, i64)> { + let mut values = values.into_iter().collect::>(); + values.sort_unstable(); + values +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ClientSessionRoute { + pub omikron_id: i64, + pub iota_id: i64, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct PresenceSubscriber { + pub user_id: i64, + pub session_id: i64, pub omikron_id: i64, } -// IotaID -> Primary OmikronID -static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); - -// IotaID -> Vec -static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); - -// UserID -> UserStatus -static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); - -// The legacy account map is kept for Iota-only/offline compatibility. Client -// transports are tracked independently: two devices must never overwrite each -// other's route merely because they authenticate as the same account. -static USER_SESSION_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); - -pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { - let mut entry = IOTA_OMIKRON_CONNECTIONS - .entry(iota_id) - .or_insert_with(Vec::new); - - if !entry.contains(&omikron_id) { - entry.push(omikron_id); - } - - if primary { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PresenceIndices { + pub sessions_by_user: HashMap>, + pub sessions_by_omikron: HashMap>, + pub targets_by_subscriber: HashMap<(i64, i64), HashSet>, + pub subscribers_by_omikron: HashMap>, + pub iotas_by_omikron: HashMap>, } -pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let mut replacement = None; - let mut connections_empty = false; - if let Some(mut entry) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { - entry.retain(|&id| id != omikron_id); - connections_empty = entry.is_empty(); - replacement = entry.first().copied(); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct IotaRoute { + pub primary_omikron_id: Option, + pub connected_omikron_ids: HashSet, +} - if connections_empty { - IOTA_OMIKRON_CONNECTIONS.remove_if(&iota_id, |_, connections| connections.is_empty()); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PresenceRoutes { + pub iotas: HashMap, + pub sessions: HashMap<(i64, i64), ClientSessionRoute>, + pub subscriptions: HashMap>, + pub indices: PresenceIndices, +} - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .is_some_and(|primary| *primary == omikron_id) - { - match replacement { - Some(omikron_id) => { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); - } - None => { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RemovedOmikronState { + pub iota_ids: Vec, + pub session_user_ids: Vec, + pub subscriber_sessions: Vec<(i64, i64)>, +} + +/// All live presence state is kept together so routing cannot be overwritten +/// by a preference update. Preferences are account-scoped, routes are +/// session-scoped, and effective state is always derived. +pub struct PresenceTracker { + pub(crate) routes: RwLock, + preferences: RwLock>, +} + +impl Default for PresenceTracker { + fn default() -> Self { + Self { + routes: RwLock::new(PresenceRoutes::default()), + preferences: RwLock::new(HashMap::new()), } } - - connections_empty } -pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { - IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) -} - -pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { - IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) -} - -pub async fn get_all_connections() --> Result>>, ()> { - let mut result: std::collections::HashMap>> = - std::collections::HashMap::new(); - - for entry in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *entry.key(); - for omikron_id in entry.value().iter() { - result - .entry(*omikron_id) - .or_insert_with(std::collections::HashMap::new) - .entry(iota_id) - .or_insert_with(Vec::new); - } - } - - for entry in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *entry.key(); - let omikron_ids = entry.value().clone(); - - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - for user in users { - let user_id = user.id.0; - if let Some(conn) = USER_STATUS_MAP.get(&user_id) { - let user_omikron_id = conn.omikron_id; - if omikron_ids.contains(&user_omikron_id) { - if let Some(iota_map) = result.get_mut(&user_omikron_id) { - if let Some(user_vec) = iota_map.get_mut(&iota_id) { - user_vec.push(user_id); - } - } +impl PresenceTracker { + fn remove_subscription_locked(routes: &mut PresenceRoutes, key: (i64, i64)) { + let Some(targets) = routes.indices.targets_by_subscriber.remove(&key) else { + return; + }; + let mut omikron_ids = HashSet::new(); + for target_user_id in targets { + if let Some(subscribers) = routes.subscriptions.get_mut(&target_user_id) { + subscribers.retain(|subscriber| { + if (subscriber.user_id, subscriber.session_id) == key { + omikron_ids.insert(subscriber.omikron_id); + false + } else { + true } + }); + if subscribers.is_empty() { + routes.subscriptions.remove(&target_user_id); + } + } + } + for omikron_id in omikron_ids { + if let Some(sessions) = routes.indices.subscribers_by_omikron.get_mut(&omikron_id) { + sessions.remove(&key); + if sessions.is_empty() { + routes.indices.subscribers_by_omikron.remove(&omikron_id); } } } } - Ok(result) -} - -pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { - USER_STATUS_MAP.insert( - user_id, - UserConnection { - connection_type: status, - omikron_id, - }, - ); -} - -pub fn track_user_session_status( - user_id: i64, - session_id: i64, - status: UserStatus, - omikron_id: i64, -) { - USER_SESSION_STATUS_MAP.insert( - (user_id, session_id), - UserConnection { - connection_type: status, - omikron_id, - }, - ); -} - -pub fn untrack_user_status(user_id: i64, omikron_id: i64) { - USER_STATUS_MAP.remove_if(&user_id, |_, connection| { - connection.omikron_id == omikron_id - }); -} - -pub fn untrack_user_session_status(user_id: i64, session_id: i64, omikron_id: i64) { - USER_SESSION_STATUS_MAP.remove_if(&(user_id, session_id), |_, connection| { - connection.omikron_id == omikron_id - }); -} - -pub fn update_user_session_status(user_id: i64, status: UserStatus) { - for mut entry in USER_SESSION_STATUS_MAP.iter_mut() { - if entry.key().0 == user_id { - entry.connection_type = status.clone(); + fn remove_session_locked( + routes: &mut PresenceRoutes, + key: (i64, i64), + expected_omikron_id: Option, + ) -> bool { + let Some(route) = routes.sessions.get(&key) else { + return false; + }; + if expected_omikron_id.is_some_and(|id| route.omikron_id != id) { + return false; } - } - // Preserve the account preference for legacy routes as well. - if let Some(mut entry) = USER_STATUS_MAP.get_mut(&user_id) { - entry.connection_type = status; - } -} - -pub fn get_user_status(user_id: i64) -> Option { - // A connected visible session is preferred. Invisible sessions remain - // routable but are intentionally presented as offline when they are the - // only active routes. - let sessions: Vec = USER_SESSION_STATUS_MAP - .iter() - .filter(|entry| entry.key().0 == user_id) - .map(|entry| entry.value().clone()) - .collect(); - if let Some(status) = sessions - .iter() - .find(|status| status.connection_type != UserStatus::user_invisible) - { - return Some(status.clone()); - } - if let Some(status) = sessions.first() { - return Some(status.clone()); - } - USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) -} - -pub fn untrack_many_users(user_ids: &[i64]) { - for user_id in user_ids { - USER_STATUS_MAP.remove(user_id); - } -} - -pub async fn untrack_omikron(omikron_id: i64) { - let mut offline_iotas = Vec::new(); - let mut primary_replacements = Vec::new(); - - for mut entry in IOTA_OMIKRON_CONNECTIONS.iter_mut() { - let iota_id = *entry.key(); - entry.retain(|&id| id != omikron_id); - if entry.is_empty() { - offline_iotas.push(iota_id); - } else if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .is_some_and(|primary| *primary == omikron_id) - { - primary_replacements.push((iota_id, entry[0])); - } - } - - for iota_id in &offline_iotas { - IOTA_OMIKRON_CONNECTIONS.remove_if(iota_id, |_, connections| connections.is_empty()); - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); - } - - for (iota_id, replacement) in primary_replacements { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, replacement); - } - - USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - USER_SESSION_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - - for iota_id in offline_iotas { - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - for user in users { - USER_STATUS_MAP.remove(&user.id.0); + let route = routes.sessions.remove(&key).unwrap(); + if let Some(session_ids) = routes.indices.sessions_by_user.get_mut(&key.0) { + session_ids.remove(&key.1); + if session_ids.is_empty() { + routes.indices.sessions_by_user.remove(&key.0); } } + if let Some(session_keys) = routes + .indices + .sessions_by_omikron + .get_mut(&route.omikron_id) + { + session_keys.remove(&key); + if session_keys.is_empty() { + routes.indices.sessions_by_omikron.remove(&route.omikron_id); + } + } + Self::remove_subscription_locked(routes, key); + true + } + + fn insert_session_locked( + routes: &mut PresenceRoutes, + key: (i64, i64), + route: ClientSessionRoute, + ) { + Self::remove_session_locked(routes, key, None); + routes.sessions.insert(key, route.clone()); + routes + .indices + .sessions_by_user + .entry(key.0) + .or_default() + .insert(key.1); + routes + .indices + .sessions_by_omikron + .entry(route.omikron_id) + .or_default() + .insert(key); + } + + fn remove_iota_owner_locked( + routes: &mut PresenceRoutes, + iota_id: i64, + omikron_id: i64, + ) -> bool { + let Some(route) = routes.iotas.get_mut(&iota_id) else { + return false; + }; + route.connected_omikron_ids.remove(&omikron_id); + route.primary_omikron_id = + select_primary_route(route.primary_omikron_id, &route.connected_omikron_ids); + let empty = route.connected_omikron_ids.is_empty(); + if let Some(iotas) = routes.indices.iotas_by_omikron.get_mut(&omikron_id) { + iotas.remove(&iota_id); + if iotas.is_empty() { + routes.indices.iotas_by_omikron.remove(&omikron_id); + } + } + if empty { + routes.iotas.remove(&iota_id); + } + empty + } + + #[cfg(debug_assertions)] + fn debug_assert_indices(routes: &PresenceRoutes) { + debug_assert!(Self::check_indices(routes).is_ok()); + } + + #[cfg(not(debug_assertions))] + fn debug_assert_indices(_: &PresenceRoutes) {} + + fn check_indices(routes: &PresenceRoutes) -> Result<(), String> { + for (key, route) in &routes.sessions { + if !routes + .indices + .sessions_by_user + .get(&key.0) + .is_some_and(|sessions| sessions.contains(&key.1)) + { + return Err(format!("session {key:?} missing user index")); + } + if !routes + .indices + .sessions_by_omikron + .get(&route.omikron_id) + .is_some_and(|sessions| sessions.contains(key)) + { + return Err(format!("session {key:?} missing Omikron index")); + } + } + for (user_id, session_ids) in &routes.indices.sessions_by_user { + for session_id in session_ids { + if !routes.sessions.contains_key(&(*user_id, *session_id)) { + return Err(format!("stale user session index {user_id}:{session_id}")); + } + } + } + for (omikron_id, session_keys) in &routes.indices.sessions_by_omikron { + for key in session_keys { + if routes.sessions.get(key).map(|route| route.omikron_id) != Some(*omikron_id) { + return Err(format!("stale Omikron session index {omikron_id}:{key:?}")); + } + } + } + for (target_user_id, subscribers) in &routes.subscriptions { + for subscriber in subscribers { + let key = (subscriber.user_id, subscriber.session_id); + if !routes + .indices + .targets_by_subscriber + .get(&key) + .is_some_and(|targets| targets.contains(target_user_id)) + { + return Err(format!("subscription {key:?} missing target index")); + } + if !routes + .indices + .subscribers_by_omikron + .get(&subscriber.omikron_id) + .is_some_and(|sessions| sessions.contains(&key)) + { + return Err(format!("subscription {key:?} missing Omikron index")); + } + } + } + for (key, targets) in &routes.indices.targets_by_subscriber { + for target in targets { + if !routes.subscriptions.get(target).is_some_and(|subscribers| { + subscribers + .iter() + .any(|subscriber| (subscriber.user_id, subscriber.session_id) == *key) + }) { + return Err(format!("stale target index {key:?}:{target}")); + } + } + } + for (omikron_id, iota_ids) in &routes.indices.iotas_by_omikron { + for iota_id in iota_ids { + if !routes + .iotas + .get(iota_id) + .is_some_and(|route| route.connected_omikron_ids.contains(omikron_id)) + { + return Err(format!("stale Iota index {omikron_id}:{iota_id}")); + } + } + } + Ok(()) + } + + pub fn check_index_consistency(&self) -> Result<(), String> { + Self::check_indices(&self.routes.read().unwrap()) + } + + pub fn set_preference(&self, user_id: i64, status: UserStatus) { + self.preferences.write().unwrap().insert(user_id, status); + } + + pub fn set_preferences(&self, preferences: HashMap) { + self.preferences.write().unwrap().extend(preferences); + } + + pub fn load_preference(&self, user_id: i64, raw: &str) -> bool { + let Some(status) = UserStatus::from_client_preference(raw) else { + self.set_preference(user_id, UserStatus::user_online); + return false; + }; + self.set_preference(user_id, status); + true + } + + pub fn preference(&self, user_id: i64) -> UserStatus { + self.preferences + .read() + .unwrap() + .get(&user_id) + .cloned() + .unwrap_or(UserStatus::user_online) + } + + pub fn has_iota_route(&self, iota_id: i64) -> bool { + self.routes + .read() + .unwrap() + .iotas + .get(&iota_id) + .is_some_and(|route| !route.connected_omikron_ids.is_empty()) + } + + pub fn has_active_session_for_iota(&self, user_id: i64, iota_id: i64) -> bool { + let routes = self.routes.read().unwrap(); + routes + .indices + .sessions_by_user + .get(&user_id) + .into_iter() + .flat_map(|session_ids| session_ids.iter()) + .any(|session_id| { + routes + .sessions + .get(&(user_id, *session_id)) + .is_some_and(|route| { + route.iota_id == iota_id + && routes.iotas.get(&route.iota_id).is_some_and(|iota| { + iota.connected_omikron_ids.contains(&route.omikron_id) + }) + }) + }) + } + + pub fn track_iota(&self, iota_id: i64, omikron_id: i64) { + self.connect_iota(iota_id, omikron_id); + } + + pub fn untrack_iota(&self, iota_id: i64, omikron_id: i64) -> bool { + self.disconnect_iota(iota_id, omikron_id) + } + + pub fn track_session(&self, user_id: i64, session_id: i64, omikron_id: i64, iota_id: i64) { + let mut routes = self.routes.write().unwrap(); + Self::insert_session_locked( + &mut routes, + (user_id, session_id), + ClientSessionRoute { + omikron_id, + iota_id, + }, + ); + Self::debug_assert_indices(&routes); + } + + pub fn remove_session(&self, user_id: i64, session_id: i64, omikron_id: i64) -> bool { + let mut routes = self.routes.write().unwrap(); + let removed = + Self::remove_session_locked(&mut routes, (user_id, session_id), Some(omikron_id)); + Self::debug_assert_indices(&routes); + removed + } + + pub fn owns_session(&self, user_id: i64, session_id: i64, omikron_id: i64) -> bool { + self.routes + .read() + .unwrap() + .sessions + .get(&(user_id, session_id)) + .is_some_and(|route| route.omikron_id == omikron_id) + } + + pub fn session_route(&self, user_id: i64, session_id: i64) -> Option { + self.routes + .read() + .unwrap() + .sessions + .get(&(user_id, session_id)) + .map(|route| route.clone()) + } + + pub fn resolve_public_state(&self, user_id: i64, iota_id: i64) -> UserStatus { + if !self.has_iota_route(iota_id) { + return UserStatus::iota_offline; + } + if !self.has_active_session_for_iota(user_id, iota_id) { + return UserStatus::user_offline; + } + self.preference(user_id).public_value() + } + + pub fn resolve_private_state(&self, user_id: i64) -> UserStatus { + self.preference(user_id) + } + + pub fn remove_omikron(&self, omikron_id: i64) -> RemovedOmikronState { + let mut routes = self.routes.write().unwrap(); + let iota_ids = routes + .indices + .iotas_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + for iota_id in &iota_ids { + Self::remove_iota_owner_locked(&mut routes, *iota_id, omikron_id); + } + + let subscriber_sessions = routes + .indices + .subscribers_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + + let session_keys = routes + .indices + .sessions_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + let session_user_ids = session_keys + .iter() + .map(|(user_id, _)| *user_id) + .collect::>(); + for key in &session_keys { + Self::remove_session_locked(&mut routes, *key, Some(omikron_id)); + } + for key in &subscriber_sessions { + Self::remove_subscription_locked(&mut routes, *key); + } + Self::debug_assert_indices(&routes); + + RemovedOmikronState { + iota_ids: sorted_vec(iota_ids), + session_user_ids: sorted_vec(session_user_ids), + subscriber_sessions: sorted_pairs(subscriber_sessions), + } + } + + pub fn remove_sessions_for_iota(&self, iota_id: i64, omikron_id: i64) { + let mut routes = self.routes.write().unwrap(); + let keys = routes + .indices + .sessions_by_omikron + .get(&omikron_id) + .into_iter() + .flat_map(|keys| keys.iter()) + .filter(|key| { + routes + .sessions + .get(key) + .is_some_and(|route| route.iota_id == iota_id) + }) + .copied() + .collect::>(); + for key in &keys { + Self::remove_session_locked(&mut routes, *key, Some(omikron_id)); + } + Self::debug_assert_indices(&routes); + } + + pub fn replace_subscription( + &self, + user_id: i64, + session_id: i64, + omikron_id: i64, + user_ids: Vec, + ) { + let mut routes = self.routes.write().unwrap(); + let key = (user_id, session_id); + Self::remove_subscription_locked(&mut routes, key); + for target_user_id in user_ids { + let subscriber = PresenceSubscriber { + user_id, + session_id, + omikron_id, + }; + routes + .subscriptions + .entry(target_user_id) + .or_default() + .insert(subscriber.clone()); + routes + .indices + .targets_by_subscriber + .entry(key) + .or_default() + .insert(target_user_id); + routes + .indices + .subscribers_by_omikron + .entry(omikron_id) + .or_default() + .insert(key); + } + Self::debug_assert_indices(&routes); + } + + pub fn remove_subscription(&self, user_id: i64, session_id: i64) { + let mut routes = self.routes.write().unwrap(); + Self::remove_subscription_locked(&mut routes, (user_id, session_id)); + Self::debug_assert_indices(&routes); + } + + pub fn subscribers(&self, target_user_id: i64) -> Vec { + let mut subscribers = self + .routes + .read() + .unwrap() + .subscriptions + .get(&target_user_id) + .cloned() + .unwrap_or_default() + .into_iter() + .collect::>(); + subscribers.sort_by_key(|subscriber| { + ( + subscriber.omikron_id, + subscriber.user_id, + subscriber.session_id, + ) + }); + subscribers + } + + pub fn sessions_owned_by(&self, omikron_id: i64) -> Vec<(i64, i64, ClientSessionRoute)> { + let routes = self.routes.read().unwrap(); + let mut sessions = routes + .indices + .sessions_by_omikron + .get(&omikron_id) + .into_iter() + .flat_map(|keys| keys.iter()) + .filter_map(|key| { + routes + .sessions + .get(key) + .map(|route| (key.0, key.1, route.clone())) + }) + .collect::>(); + sessions.sort_by_key(|(user_id, session_id, _)| (*user_id, *session_id)); + sessions + } + + pub fn replace_routes_owned_by( + &self, + omikron_id: i64, + iota_ids: &[i64], + sessions: &[(i64, i64, i64)], + ) { + self.replace_omikron_snapshot(omikron_id, iota_ids, sessions); + } + + pub fn replace_omikron_snapshot( + &self, + omikron_id: i64, + iota_ids: &[i64], + sessions: &[(i64, i64, i64)], + ) { + let mut routes = self.routes.write().unwrap(); + let old_sessions = routes + .indices + .sessions_by_omikron + .get(&omikron_id) + .cloned() + .unwrap_or_default(); + let old_subscriber_sessions = routes + .indices + .subscribers_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + for key in &old_subscriber_sessions { + Self::remove_subscription_locked(&mut routes, *key); + } + for key in &old_sessions { + Self::remove_session_locked(&mut routes, *key, Some(omikron_id)); + } + let old_iota_ids = routes + .indices + .iotas_by_omikron + .get(&omikron_id) + .cloned() + .unwrap_or_default(); + for iota_id in old_iota_ids { + Self::remove_iota_owner_locked(&mut routes, iota_id, omikron_id); + } + for iota_id in iota_ids { + let route = routes.iotas.entry(*iota_id).or_default(); + route.connected_omikron_ids.insert(omikron_id); + route.primary_omikron_id = + select_primary_route(route.primary_omikron_id, &route.connected_omikron_ids); + routes + .indices + .iotas_by_omikron + .entry(omikron_id) + .or_default() + .insert(*iota_id); + } + for (user_id, session_id, iota_id) in sessions { + Self::insert_session_locked( + &mut routes, + (*user_id, *session_id), + ClientSessionRoute { + omikron_id, + iota_id: *iota_id, + }, + ); + } + Self::debug_assert_indices(&routes); + } + + pub fn iota_ids_owned_by(&self, omikron_id: i64) -> Vec { + let routes = self.routes.read().unwrap(); + sorted_vec( + routes + .indices + .iotas_by_omikron + .get(&omikron_id) + .cloned() + .unwrap_or_default(), + ) + } + + pub fn track_iota_connection(&self, iota_id: i64, omikron_id: i64, primary: bool) { + self.connect_iota_with_priority(iota_id, omikron_id, primary); + } + + pub fn untrack_iota_connection(&self, iota_id: i64, omikron_id: i64) -> bool { + let connections_empty = self.disconnect_iota(iota_id, omikron_id); + self.remove_sessions_for_iota(iota_id, omikron_id); + connections_empty + } + + pub fn primary_iota_route(&self, iota_id: i64) -> Option { + self.routes + .read() + .unwrap() + .iotas + .get(&iota_id) + .and_then(|route| route.primary_omikron_id) + } + + pub fn iota_connections(&self, iota_id: i64) -> Option> { + self.all_iota_routes().get(&iota_id).map(|route| { + let mut ids = route + .connected_omikron_ids + .iter() + .copied() + .collect::>(); + ids.sort_unstable(); + ids + }) + } + + pub fn routes_for_user(&self, user_id: i64) -> Vec { + let routes = self.routes.read().unwrap(); + let mut user_routes = routes + .indices + .sessions_by_user + .get(&user_id) + .into_iter() + .flat_map(|session_ids| session_ids.iter()) + .filter_map(|session_id| { + routes + .sessions + .get(&(user_id, *session_id)) + .map(|route| (*session_id, route.clone())) + }) + .collect::>(); + user_routes.sort_by_key(|(session_id, route)| (*session_id, route.omikron_id)); + user_routes.into_iter().map(|(_, route)| route).collect() + } + + pub fn sessions_for_user(&self, user_id: i64) -> Vec<(i64, ClientSessionRoute)> { + let routes = self.routes.read().unwrap(); + let mut sessions = routes + .indices + .sessions_by_user + .get(&user_id) + .into_iter() + .flat_map(|session_ids| session_ids.iter()) + .filter_map(|session_id| { + routes + .sessions + .get(&(user_id, *session_id)) + .map(|route| (*session_id, route.clone())) + }) + .collect::>(); + sessions.sort_by_key(|(session_id, route)| (*session_id, route.omikron_id)); + sessions + } + + pub fn route_for_session(&self, user_id: i64, session_id: i64) -> Option { + self.session_route(user_id, session_id) + } + + pub fn user_route(&self, user_id: i64) -> Option { + self.routes_for_user(user_id).into_iter().next() + } + + pub fn connect_iota(&self, iota_id: i64, omikron_id: i64) { + self.connect_iota_with_priority(iota_id, omikron_id, false); + } + + fn connect_iota_with_priority(&self, iota_id: i64, omikron_id: i64, primary: bool) { + let mut routes = self.routes.write().unwrap(); + let route = routes.iotas.entry(iota_id).or_default(); + route.connected_omikron_ids.insert(omikron_id); + if primary || route.primary_omikron_id.is_none() { + route.primary_omikron_id = Some(omikron_id); + } + routes + .indices + .iotas_by_omikron + .entry(omikron_id) + .or_default() + .insert(iota_id); + Self::debug_assert_indices(&routes); + } + + pub fn disconnect_iota(&self, iota_id: i64, omikron_id: i64) -> bool { + let mut routes = self.routes.write().unwrap(); + let empty = Self::remove_iota_owner_locked(&mut routes, iota_id, omikron_id); + Self::debug_assert_indices(&routes); + empty + } + + pub fn all_iota_routes(&self) -> HashMap { + self.routes.read().unwrap().iotas.clone() + } + + pub fn connection_routes(&self) -> HashMap>> { + let mut result: std::collections::HashMap>> = + std::collections::HashMap::new(); + + let iotas = self.all_iota_routes(); + for (iota_id, route) in &iotas { + for omikron_id in &route.connected_omikron_ids { + result + .entry(*omikron_id) + .or_default() + .entry(*iota_id) + .or_default(); + } + } + result + } + + pub fn remove_omikron_and_offline_users(&self, omikron_id: i64) -> RemovedOmikronState { + self.remove_omikron(omikron_id) + } +} + +#[cfg(test)] +mod tests { + use super::{ClientSessionRoute, PresenceTracker}; + use crate::sql::connection_status::UserStatus; + + #[test] + fn effective_state_is_derived_from_routes_and_preference() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_online); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::iota_offline + ); + + tracker.track_iota(11, 42); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::user_offline + ); + + tracker.track_session(7, 3, 42, 11); + assert_eq!(tracker.resolve_public_state(7, 11), UserStatus::user_online); + tracker.set_preference(7, UserStatus::user_invisible); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::user_offline + ); + } + + #[test] + fn session_must_match_assigned_iota_and_live_route() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_session(7, 3, 42, 12); + assert!(!tracker.has_active_session_for_iota(7, 11)); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::user_offline + ); + + tracker.track_session(7, 3, 43, 11); + assert!(!tracker.has_active_session_for_iota(7, 11)); + + tracker.track_session(7, 3, 42, 11); + assert!(tracker.has_active_session_for_iota(7, 11)); + tracker.disconnect_iota(11, 42); + assert!(!tracker.has_active_session_for_iota(7, 11)); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::iota_offline + ); + } + + #[test] + fn resolver_covers_all_public_preference_states() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_session(7, 3, 42, 11); + + for (preference, expected) in [ + (UserStatus::user_online, UserStatus::user_online), + (UserStatus::user_idle, UserStatus::user_idle), + (UserStatus::user_dnd, UserStatus::user_dnd), + (UserStatus::user_wc, UserStatus::user_wc), + (UserStatus::user_invisible, UserStatus::user_offline), + ] { + tracker.set_preference(7, preference); + assert_eq!(tracker.resolve_public_state(7, 11), expected); + } + } + + #[test] + fn session_routes_replace_by_session_without_replacing_preferences() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_dnd); + tracker.track_iota(11, 42); + tracker.track_iota(12, 43); + tracker.track_session(7, 3, 42, 11); + tracker.track_session(7, 4, 43, 12); + + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + assert_eq!( + tracker.session_route(7, 3), + Some(ClientSessionRoute { + omikron_id: 42, + iota_id: 11, + }) + ); + + tracker.remove_session(7, 3, 42); + assert!(!tracker.owns_session(7, 3, 42)); + assert!(tracker.owns_session(7, 4, 43)); + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + } + + #[test] + fn subscriptions_are_authoritative_and_cleanup_is_scoped() { + let tracker = PresenceTracker::default(); + tracker.replace_subscription(7, 3, 42, vec![20, 21, 20]); + tracker.replace_subscription(7, 3, 42, vec![21, 22]); + tracker.replace_subscription(8, 4, 43, vec![21]); + + assert_eq!(tracker.subscribers(20).len(), 0); + assert_eq!(tracker.subscribers(21).len(), 2); + assert_eq!(tracker.subscribers(22).len(), 1); + + tracker.remove_omikron(42); + assert!(tracker.subscribers(22).is_empty()); + assert_eq!(tracker.subscribers(21).len(), 1); + } + + #[test] + fn losing_one_iota_route_keeps_iota_online() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_iota(11, 43); + assert!(tracker.has_iota_route(11)); + + assert!(!tracker.untrack_iota(11, 42)); + assert!(tracker.has_iota_route(11)); + assert!(tracker.untrack_iota(11, 43)); + assert!(!tracker.has_iota_route(11)); + } + + #[test] + fn iota_connection_routes_track_primary_and_replacement() { + let tracker = PresenceTracker::default(); + tracker.track_iota_connection(11, 42, true); + tracker.track_iota_connection(11, 43, false); + + assert_eq!(tracker.iota_connections(11), Some(vec![42, 43])); + assert_eq!(tracker.primary_iota_route(11), Some(42)); + + tracker.untrack_iota_connection(11, 42); + + assert_eq!(tracker.iota_connections(11), Some(vec![43])); + assert_eq!(tracker.primary_iota_route(11), Some(43)); + } + + #[test] + fn snapshot_preserves_primary_and_uses_deterministic_fallback() { + let tracker = PresenceTracker::default(); + tracker.connect_iota(11, 20); + tracker.connect_iota(11, 30); + assert_eq!(tracker.primary_iota_route(11), Some(20)); + + tracker.replace_omikron_snapshot(30, &[11], &[]); + assert_eq!(tracker.primary_iota_route(11), Some(20)); + + tracker.replace_omikron_snapshot(20, &[11], &[]); + assert_eq!(tracker.primary_iota_route(11), Some(30)); + tracker.replace_omikron_snapshot(30, &[11], &[]); + assert_eq!(tracker.primary_iota_route(11), Some(20)); + } + + #[test] + fn user_routes_are_sorted_by_session_then_omikron() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 20, 30, 11); + tracker.track_session(7, 10, 40, 11); + assert_eq!( + tracker + .routes_for_user(7) + .iter() + .map(|route| route.omikron_id) + .collect::>(), + vec![40, 30] + ); + + tracker.remove_session(7, 10, 40); + assert_eq!(tracker.routes_for_user(7).len(), 1); + } + + #[test] + fn stale_disconnect_cannot_remove_current_session_subscription() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 3, 42, 11); + tracker.replace_subscription(7, 3, 42, vec![20]); + + assert!(!tracker.remove_session(7, 3, 99)); + assert!(tracker.owns_session(7, 3, 42)); + assert_eq!(tracker.subscribers(20).len(), 1); + + assert!(tracker.remove_session(7, 3, 42)); + assert!(tracker.subscribers(20).is_empty()); + } + + #[test] + fn private_state_is_not_affected_by_connectivity() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_invisible); + assert_eq!(tracker.resolve_private_state(7), UserStatus::user_invisible); + } + + #[test] + fn invalid_persisted_preference_falls_back_to_online() { + let tracker = PresenceTracker::default(); + assert!(!tracker.load_preference(7, "user_offline")); + assert_eq!(tracker.preference(7), UserStatus::user_online); + assert!(!tracker.load_preference(7, "iota_offline")); + assert_eq!(tracker.preference(7), UserStatus::user_online); + assert!(tracker.load_preference(7, "user_invisible")); + assert_eq!(tracker.preference(7), UserStatus::user_invisible); + assert!(tracker.load_preference(7, "user_dnd")); + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + } + + #[test] + fn active_session_can_use_one_of_several_live_iota_routes() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_iota(11, 43); + tracker.track_session(7, 3, 43, 11); + + assert!(tracker.has_active_session_for_iota(7, 11)); + tracker.disconnect_iota(11, 43); + assert!(!tracker.has_active_session_for_iota(7, 11)); + } + + #[test] + fn route_snapshot_replaces_only_one_omikron() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_iota(12, 43); + tracker.track_session(7, 3, 42, 11); + tracker.track_session(8, 4, 43, 12); + tracker.replace_subscription(7, 3, 42, vec![20]); + + tracker.replace_routes_owned_by(42, &[12], &[(9, 5, 12)]); + + assert!(!tracker.owns_session(7, 3, 42)); + assert!(tracker.owns_session(8, 4, 43)); + assert!(tracker.owns_session(9, 5, 42)); + assert!(tracker.subscribers(20).is_empty()); + assert!(tracker.has_iota_route(12)); + } + + #[test] + fn omikron_cleanup_preserves_other_routes_and_preferences() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_dnd); + tracker.track_iota(11, 42); + tracker.track_iota(11, 43); + tracker.track_session(7, 3, 42, 11); + tracker.track_session(7, 4, 43, 11); + tracker.replace_subscription(7, 3, 42, vec![20]); + tracker.replace_subscription(7, 4, 43, vec![21]); + + tracker.remove_omikron(42); + + assert!(tracker.has_iota_route(11)); + assert!(!tracker.owns_session(7, 3, 42)); + assert!(tracker.owns_session(7, 4, 43)); + assert!(tracker.subscribers(20).is_empty()); + assert_eq!(tracker.subscribers(21).len(), 1); + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + } + + #[test] + fn secondary_indices_follow_session_and_subscription_mutations() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 3, 42, 11); + tracker.replace_subscription(7, 3, 42, vec![20, 21, 20]); + assert_eq!( + tracker + .routes + .read() + .unwrap() + .indices + .sessions_by_user + .get(&7) + .cloned(), + Some([3].into_iter().collect()) + ); + assert_eq!( + tracker + .routes + .read() + .unwrap() + .indices + .targets_by_subscriber + .get(&(7, 3)) + .cloned(), + Some([20, 21].into_iter().collect()) + ); + assert!(tracker.check_index_consistency().is_ok()); + + tracker.replace_subscription(7, 3, 42, vec![22]); + assert!(tracker.subscribers(20).is_empty()); + assert!(tracker.subscribers(21).is_empty()); + assert_eq!(tracker.subscribers(22).len(), 1); + tracker.remove_session(7, 3, 42); + assert!(tracker.check_index_consistency().is_ok()); + assert!(tracker + .routes + .read() + .unwrap() + .indices + .sessions_by_user + .is_empty()); + assert!(tracker + .routes + .read() + .unwrap() + .indices + .targets_by_subscriber + .is_empty()); + } + + #[test] + fn omikron_removal_returns_all_affected_identifiers() { + let tracker = PresenceTracker::default(); + tracker.connect_iota(11, 42); + tracker.track_session(7, 3, 42, 11); + tracker.replace_subscription(7, 3, 42, vec![20]); + + let removed = tracker.remove_omikron(42); + + assert_eq!(removed.iota_ids, vec![11]); + assert_eq!(removed.session_user_ids, vec![7]); + assert_eq!(removed.subscriber_sessions, vec![(7, 3)]); + assert!(tracker.check_index_consistency().is_ok()); + } + + #[test] + fn consistency_check_reports_corrupted_reverse_index() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 3, 42, 11); + tracker + .routes + .write() + .unwrap() + .indices + .sessions_by_user + .get_mut(&7) + .unwrap() + .clear(); + + assert!(tracker.check_index_consistency().is_err()); } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..dc91021 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,72 @@ +use crate::sql::user_online_tracker::PresenceTracker; +use std::sync::Arc; + +pub struct OmegaState { + pub presence: Arc, +} + +impl Default for OmegaState { + fn default() -> Self { + Self { + presence: Arc::new(PresenceTracker::default()), + } + } +} + +impl OmegaState { + pub fn new() -> Arc { + Arc::new(Self::default()) + } +} + +#[cfg(test)] +mod tests { + use super::OmegaState; + use crate::sql::connection_status::UserStatus; + + #[test] + fn state_instances_have_independent_presence_trackers() { + let first = OmegaState::new(); + let second = OmegaState::new(); + + first.presence.track_iota_connection(11, 42, true); + + assert!(first.presence.has_iota_route(11)); + assert!(!second.presence.has_iota_route(11)); + assert_eq!(first.presence.primary_iota_route(11), Some(42)); + assert_eq!(second.presence.primary_iota_route(11), None); + } + + #[test] + fn two_session_private_and_public_presence_flow_is_authoritative() { + let state = OmegaState::new(); + state.presence.set_preference(7, UserStatus::user_online); + state.presence.set_preference(8, UserStatus::user_online); + state.presence.track_iota_connection(11, 42, true); + state.presence.track_session(7, 100, 42, 11); + state.presence.track_session(7, 101, 42, 11); + state.presence.replace_subscription(7, 100, 42, vec![8]); + + assert_eq!( + state.presence.resolve_public_state(8, 11), + UserStatus::user_offline + ); + state.presence.set_preference(8, UserStatus::user_invisible); + assert_eq!( + state.presence.resolve_public_state(8, 11), + UserStatus::user_offline + ); + assert_eq!( + state.presence.resolve_private_state(8), + UserStatus::user_invisible + ); + + state.presence.remove_session(7, 100, 42); + assert!(state.presence.owns_session(7, 101, 42)); + state.presence.remove_session(7, 101, 42); + assert_eq!( + state.presence.resolve_public_state(7, 11), + UserStatus::user_offline + ); + } +} diff --git a/src/transport/capabilities.rs b/src/transport/capabilities.rs new file mode 100644 index 0000000..0a3c685 --- /dev/null +++ b/src/transport/capabilities.rs @@ -0,0 +1,147 @@ +use std::collections::BTreeSet; + +const OMIKRON_PREFIX: &str = "omikron;caps="; +const OMEGA_PREFIX: &str = "omega;caps="; +const SET_USER_STATE: &str = "set_user_state_v1"; +const STATE_SUBSCRIBE: &str = "state_subscribe_v1"; +const SESSION_SNAPSHOT: &str = "session_snapshot_v1"; +const CLIENT_STATE_PUSH: &str = "client_state_push_v1"; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PeerCapabilities { + pub set_user_state_v1: bool, + pub state_subscribe_v1: bool, + pub session_snapshot_v1: bool, + pub client_state_push_v1: bool, +} + +impl PeerCapabilities { + /// A missing descriptor is the legacy protocol: tuple route snapshots, + /// GetStates-only subscription refreshes, and ClientChanged pushes. + pub fn from_identification_description(description: Option<&str>) -> Result { + parse_capabilities(description, OMIKRON_PREFIX) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct OmegaCapabilities { + pub set_user_state_v1: bool, + pub state_subscribe_v1: bool, + pub session_snapshot_v1: bool, + pub client_state_push_v1: bool, +} + +impl OmegaCapabilities { + pub fn current() -> Self { + Self { + set_user_state_v1: true, + state_subscribe_v1: true, + session_snapshot_v1: true, + client_state_push_v1: true, + } + } + + pub fn identification_description(&self) -> String { + let mut names = Vec::new(); + if self.set_user_state_v1 { + names.push(SET_USER_STATE); + } + if self.state_subscribe_v1 { + names.push(STATE_SUBSCRIBE); + } + if self.session_snapshot_v1 { + names.push(SESSION_SNAPSHOT); + } + if self.client_state_push_v1 { + names.push(CLIENT_STATE_PUSH); + } + format!("{OMEGA_PREFIX}{}", names.join(",")) + } +} + +fn parse_capabilities(description: Option<&str>, prefix: &str) -> Result { + let Some(description) = description else { + return Ok(PeerCapabilities::default()); + }; + if description == "omikron" { + return Ok(PeerCapabilities::default()); + } + let Some(capabilities) = description.strip_prefix(prefix) else { + return Err(()); + }; + let mut seen = BTreeSet::new(); + for capability in capabilities.split(',') { + if capability.is_empty() || !seen.insert(capability) { + return Err(()); + } + } + if seen.iter().any(|capability| { + !matches!( + *capability, + SET_USER_STATE | STATE_SUBSCRIBE | SESSION_SNAPSHOT | CLIENT_STATE_PUSH + ) + }) { + return Err(()); + } + Ok(PeerCapabilities { + set_user_state_v1: seen.contains(SET_USER_STATE), + state_subscribe_v1: seen.contains(STATE_SUBSCRIBE), + session_snapshot_v1: seen.contains(SESSION_SNAPSHOT), + client_state_push_v1: seen.contains(CLIENT_STATE_PUSH), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn advertised_capabilities_are_parsed() { + let capabilities = PeerCapabilities::from_identification_description(Some( + "omikron;caps=set_user_state_v1,state_subscribe_v1,session_snapshot_v1,client_state_push_v1", + )).unwrap(); + assert!(capabilities.set_user_state_v1); + assert!(capabilities.state_subscribe_v1); + assert!(capabilities.session_snapshot_v1); + assert!(capabilities.client_state_push_v1); + } + + #[test] + fn invalid_capability_values_fail_identification() { + assert!( + PeerCapabilities::from_identification_description(Some("omikron;caps=unsupported")) + .is_err() + ); + } + + #[test] + fn legacy_peer_has_no_version_specific_features() { + let capabilities = PeerCapabilities::from_identification_description(None).unwrap(); + assert!(!capabilities.set_user_state_v1); + assert!(!capabilities.state_subscribe_v1); + assert!(!capabilities.session_snapshot_v1); + assert!(!capabilities.client_state_push_v1); + assert_eq!( + PeerCapabilities::from_identification_description(Some("omikron")), + Ok(PeerCapabilities::default()) + ); + } + + #[test] + fn reconnecting_with_the_same_identification_is_stable() { + let description = Some( + "omikron;caps=set_user_state_v1,state_subscribe_v1,session_snapshot_v1,client_state_push_v1", + ); + assert_eq!( + PeerCapabilities::from_identification_description(description), + PeerCapabilities::from_identification_description(description) + ); + } + + #[test] + fn omega_capability_description_is_distinct_from_omikron_capabilities() { + let description = OmegaCapabilities::current().identification_description(); + assert!(description.starts_with(OMEGA_PREFIX)); + assert!(PeerCapabilities::from_identification_description(Some(&description)).is_err()); + } +} diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index adaa1a2..0c31904 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -1,69 +1,207 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; use crate::{ - db::user_repo, - log_in, - models::IotaId, - sql::{connection_status::UserStatus, user_online_tracker}, + db::user_repo, log_in, models::IotaId, sql::connection_status::UserStatus, state::OmegaState, }; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use std::sync::Arc; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + sync::Arc, +}; -pub async fn user_connected( - _connection: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "User connected"); - if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - let status = value - .get_data(DataType::UserState) - .as_str() - .and_then(UserStatus::from_str) - .unwrap_or(UserStatus::user_online); - if let Ok(user_id) = i64::try_from(user_id) { - if let Some(session_id) = value - .get_data(DataType::SessionId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) - { - user_online_tracker::track_user_session_status( - user_id, session_id, status, omikron_id, - ); - } else { - user_online_tracker::track_user_status(user_id, status, omikron_id); - } +fn parse_subscription(value: &CommunicationValue) -> Result<(i64, i64, Vec), &'static str> { + let user_id = i64::try_from(value.get_sender()) + .ok() + .filter(|id| *id > 0) + .ok_or("user_id")?; + let session_id = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + .ok_or("session_id")?; + let DataValue::Array(values) = value.get_data(DataType::UserIds) else { + return Err("user_ids"); + }; + + let mut user_ids = Vec::with_capacity(values.len()); + for value in values { + let DataValue::SignedNumber(user_id) = value else { + return Err("user_ids"); + }; + let Ok(user_id) = i64::try_from(*user_id) else { + return Err("user_ids"); + }; + if user_id <= 0 { + return Err("user_ids"); + } + if !user_ids.contains(&user_id) { + user_ids.push(user_id); } } - Ok(()) + Ok((user_id, session_id, user_ids)) } -pub async fn user_disconnected( - _: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); - if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - if let Some(session_id) = value - .get_data(DataType::SessionId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) +fn apply_preferences(state: &OmegaState, preferences: HashMap) { + state.presence.set_preferences(preferences); +} + +fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMap { + users + .iter() + .map(|user| { + ( + user.id.0, + state + .presence + .resolve_public_state(user.id.0, user.iota_id.0), + ) + }) + .collect() +} + +fn changed_states( + state: &OmegaState, + before: &HashMap, + users: &[crate::models::User], +) -> Vec<(i64, UserStatus)> { + let mut changes = users + .iter() + .filter_map(|user| { + let after = state + .presence + .resolve_public_state(user.id.0, user.iota_id.0); + (before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after)) + }) + .collect::>(); + changes.sort_by_key(|(user_id, _)| *user_id); + changes.dedup_by_key(|(user_id, _)| *user_id); + changes +} + +fn state_notification( + subscriber: &crate::sql::user_online_tracker::PresenceSubscriber, + user_id: i64, + user_state: &UserStatus, +) -> CommunicationValue { + CommunicationValue::new(CommunicationType::ClientChanged) + .with_receiver(subscriber.user_id as u64) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(subscriber.session_id.into()), + ) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) +} + +fn private_state_notification( + user_id: i64, + session_id: i64, + user_state: &UserStatus, +) -> CommunicationValue { + CommunicationValue::new(CommunicationType::ClientChanged) + .with_receiver(user_id as u64) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id.into()), + ) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) +} + +async fn publish_state_changes(state: &OmegaState, changes: &[(i64, UserStatus)]) { + let mut grouped = BTreeMap::>::new(); + for (user_id, user_state) in changes { + for subscriber in state.presence.subscribers(*user_id) { + grouped + .entry(subscriber.omikron_id) + .or_default() + .push(state_notification(&subscriber, *user_id, user_state)); + } + } + for (omikron_id, notifications) in grouped { + if let Err(error) = + crate::transport::omikron_manager::send_state_batch(omikron_id, notifications).await { - user_online_tracker::untrack_user_session_status( - user_id as i64, - session_id, + log_in!( + crate::util::logger::PrintType::General, + "Failed to deliver presence state batch to Omikron {}: {}", omikron_id, + error ); - } else { - user_online_tracker::untrack_user_status(user_id as i64, omikron_id); } } - Ok(()) } -pub async fn client_changed( +async fn publish_changed_states( + state: &OmegaState, + before: &HashMap, + users: &[crate::models::User], +) { + publish_state_changes(state, &changed_states(state, before, users)).await; +} + +async fn publish_private_state(state: &OmegaState, user_id: i64, user_state: &UserStatus) { + let mut grouped = BTreeMap::>::new(); + for (session_id, route) in state.presence.sessions_for_user(user_id) { + grouped + .entry(route.omikron_id) + .or_default() + .push(private_state_notification(user_id, session_id, user_state)); + } + for (omikron_id, notifications) in grouped { + if let Err(error) = + crate::transport::omikron_manager::send_state_batch(omikron_id, notifications).await + { + log_in!( + crate::util::logger::PrintType::General, + "Failed to deliver private presence state batch to Omikron {}: {}", + omikron_id, + error + ); + } + } +} + +pub async fn state_subscribe( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + let (user_id, session_id, user_ids) = match parse_subscription(&value) { + Ok(subscription) => subscription, + Err("user_id") => { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) + .await; + } + Err(detail) => { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + detail, + ) + .await; + } + }; + if !state.presence.owns_session(user_id, session_id, omikron_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + state + .presence + .replace_subscription(user_id, session_id, omikron_id, user_ids); + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await +} + +/// Legacy state-change contract used by Omegas that predate SetUserState. +/// The payload is ClientChanged with UserId and UserState only. +pub async fn client_changed_legacy( + state: Arc, _: Arc, value: CommunicationValue, _: i64, @@ -72,29 +210,266 @@ pub async fn client_changed( .get_data(DataType::UserId) .as_number() .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { return Ok(()); }; let Some(status) = value .get_data(DataType::UserState) .as_str() - .and_then(UserStatus::from_str) + .and_then(UserStatus::from_client_preference) else { return Ok(()); }; - // Connectivity is derived from routes. Clients may choose only public - // presence preferences, never server/offline states. - if matches!( - status, - UserStatus::user_offline | UserStatus::iota_offline | UserStatus::iota_online - ) { - return Ok(()); - } - user_online_tracker::update_user_session_status(user_id, status); + state.presence.set_preference(user_id, status); Ok(()) } +pub async fn user_connected( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User connected"); + let Some(user_id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let user = match user_repo::get_by_user_id(user_id.into()).await { + Ok(user) => user, + Err(crate::error::OmegaError::NotFound) => { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + } + Err(error) => return Err(error.into()), + }; + let preferences = match user_repo::get_presence_preferences(&[user_id]).await { + Ok(preferences) => preferences, + Err(error) => return Err(error.into()), + }; + if user.iota_id.0 != iota_id || !state.presence.has_iota_route(iota_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + apply_preferences(&state, preferences); + let users = [user]; + let before = states_for_users(&state, &users); + state + .presence + .track_session(user_id, session_id, omikron_id, iota_id); + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await +} + +pub async fn user_disconnected( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); + let Some(user_id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + if let Ok(user) = user_repo::get_by_user_id(user_id.into()).await { + let preferences = user_repo::get_presence_preferences(&[user_id]).await?; + apply_preferences(&state, preferences); + let users = [user]; + let before = states_for_users(&state, &users); + state + .presence + .remove_session(user_id, session_id, omikron_id); + publish_changed_states(&state, &before, &users).await; + } else { + state + .presence + .remove_session(user_id, session_id, omikron_id); + } + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await +} + +pub async fn set_user_state( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + let Some(user_id) = i64::try_from(value.get_sender()).ok().filter(|id| *id > 0) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) + .await; + }; + if let Some(requested_user) = value.get_data_opt(DataType::UserId) { + let Some(requested_user_id) = requested_user + .as_number() + .and_then(|id| i64::try_from(id).ok()) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "user_id", + ) + .await; + }; + if requested_user_id != user_id { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "user_id", + ) + .await; + } + } + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "iota_id", + ) + .await; + }; + let Some(requested_state) = value + .get_data(DataType::UserState) + .as_str() + .and_then(UserStatus::from_client_preference) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "user_state", + ) + .await; + }; + if !state.presence.has_iota_route(iota_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "session_id", + ) + .await; + }; + let Some(route) = state.presence.session_route(user_id, session_id) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + }; + if route.omikron_id != omikron_id || route.iota_id != iota_id { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + } + if !state.presence.has_active_session_for_iota(user_id, iota_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + let previous_preference = state.presence.preference(user_id); + let previous_state = state.presence.resolve_public_state(user_id, iota_id); + if let Err(error) = + user_repo::change_presence_preference(user_id.into(), requested_state.to_string()).await + { + log_in!( + crate::util::logger::PrintType::General, + "Failed to persist presence preference: {}", + error + ); + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .await; + } + state + .presence + .set_preference(user_id, requested_state.clone()); + let new_state = state.presence.resolve_public_state(user_id, iota_id); + if requested_state != previous_preference { + publish_private_state(&state, user_id, &requested_state).await; + } + if requested_state != previous_preference && new_state != previous_state { + publish_state_changes(&state, &[(user_id, new_state)]).await; + } + connection + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.get_id()) + .add_typed_default( + DataType::UserState, + DataValue::Str(requested_state.to_string()), + ), + ) + .await +} + pub async fn iota_connected( + state: Arc, connection: Arc, value: CommunicationValue, omikron_id: i64, @@ -105,36 +480,32 @@ pub async fn iota_connected( .as_number() .map(|id| id as i64) else { - return Ok(()); + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; }; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - let mut user_ids = Vec::new(); - match user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - Ok(users) => { - for user in users { - user_ids.push(DataValue::SignedNumber(user.id.0.into())); - user_online_tracker::track_user_status( - user.id.0, - UserStatus::user_offline, - omikron_id, - ); - } - } - Err(_) => log_in!( - crate::util::logger::PrintType::General, - "SQL error loading users for IOTA" - ), - } + let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; + let ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences(&state, user_repo::get_presence_preferences(&ids).await?); + let before = states_for_users(&state, &users); + state.presence.connect_iota(iota_id, omikron_id); + let user_ids = users + .iter() + .map(|user| DataValue::SignedNumber(user.id.0.into())) + .collect(); let response = CommunicationValue::new(CommunicationType::IotaUserData) - .with_id(value.get_id()) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); - let _ = connection.send(&response).await; - Ok(()) + connection.clone().send(&response).await?; + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await } pub async fn iota_disconnected( - _: Arc, + state: Arc, + connection: Arc, value: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { @@ -144,40 +515,317 @@ pub async fn iota_disconnected( .as_number() .map(|id| id as i64) else { - return Ok(()); + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; }; - if user_online_tracker::untrack_iota_connection(iota_id, omikron_id) { - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - user_online_tracker::untrack_many_users( - &users.iter().map(|user| user.id.0).collect::>(), - ); - } - } - Ok(()) + let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; + let ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences(&state, user_repo::get_presence_preferences(&ids).await?); + let before = states_for_users(&state, &users); + state.presence.untrack_iota_connection(iota_id, omikron_id); + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await } pub async fn sync_status( - _: Arc, + state: Arc, + connection: Arc, value: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { - if let DataValue::Array(ids) = value.get_data(DataType::UserIds) { - for id in ids { - if let DataValue::SignedNumber(id) = id { - user_online_tracker::track_user_status( - *id as i64, - UserStatus::user_offline, - omikron_id, - ); - } + let request_id = value.get_id(); + let DataValue::Array(iota_values) = value.get_data(DataType::IotaIds) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let DataValue::Array(session_values) = value.get_data(DataType::UserStates) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + + let mut iota_ids = Vec::with_capacity(iota_values.len()); + for item in iota_values { + let DataValue::SignedNumber(id) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let Ok(id) = i64::try_from(*id) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + if id <= 0 { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + if !iota_ids.contains(&id) { + iota_ids.push(id); } } - if let DataValue::Array(ids) = value.get_data(DataType::IotaIds) { - for id in ids { - if let DataValue::SignedNumber(id) = id { - user_online_tracker::track_iota_connection(*id as i64, omikron_id, true); + + if !connection.peer_capabilities().session_snapshot_v1 { + let DataValue::Array(user_values) = value.get_data(DataType::UserIds) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let mut user_ids = Vec::with_capacity(user_values.len()); + for item in user_values { + let DataValue::SignedNumber(user_id) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let Ok(user_id) = i64::try_from(*user_id) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + if user_id <= 0 { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + if !user_ids.contains(&user_id) { + user_ids.push(user_id); } } + + let previous_iota_ids = state.presence.iota_ids_owned_by(omikron_id); + let affected_iota_ids = previous_iota_ids + .iter() + .chain(iota_ids.iter()) + .copied() + .collect::>(); + let users = user_repo::get_users_by_ids(&user_ids).await?; + let returned_user_ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences( + &state, + user_repo::get_presence_preferences(&returned_user_ids).await?, + ); + let before = states_for_users(&state, &users); + state + .presence + .replace_omikron_snapshot(omikron_id, &iota_ids, &[]); + let affected_users = user_repo::get_users_by_ids_and_iota_ids( + &returned_user_ids, + &affected_iota_ids.iter().copied().collect::>(), + ) + .await?; + publish_changed_states(&state, &before, &affected_users).await; + return connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(request_id)) + .await; + } + + let tm = mtp::type_map::TypeMap::latest(); + let mut sessions = Vec::with_capacity(session_values.len()); + for item in session_values { + let (user_id, session_id, iota_id) = if connection.peer_capabilities().session_snapshot_v1 { + let DataValue::Container(entries) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let find = |kind| { + entries.iter().find_map(|(key, value)| { + (Some(*key) == DataType::try_to_id(kind, &tm)).then_some(value) + }) + }; + let ( + Some(DataValue::SignedNumber(user_id)), + Some(DataValue::SignedNumber(session_id)), + Some(DataValue::SignedNumber(iota_id)), + ) = ( + find(DataType::UserId), + find(DataType::SessionId), + find(DataType::IotaId), + ) + else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + (*user_id, *session_id, *iota_id) + } else { + let DataValue::Array(values) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let [ + DataValue::SignedNumber(user_id), + DataValue::SignedNumber(session_id), + DataValue::SignedNumber(iota_id), + ] = values.as_slice() + else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + (*user_id, *session_id, *iota_id) + }; + let (Ok(user_id), Ok(session_id), Ok(iota_id)) = ( + i64::try_from(user_id), + i64::try_from(session_id), + i64::try_from(iota_id), + ) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + if user_id <= 0 || session_id <= 0 || iota_id <= 0 || !iota_ids.contains(&iota_id) { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + if sessions.iter().any(|(existing_user, existing_session, _)| { + *existing_user == user_id && *existing_session == session_id + }) { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + sessions.push((user_id, session_id, iota_id)); + } + + let previous_iota_ids = state.presence.iota_ids_owned_by(omikron_id); + let previous_session_user_ids = state + .presence + .sessions_owned_by(omikron_id) + .into_iter() + .map(|(user_id, _, _)| user_id) + .collect::>(); + let new_session_user_ids = sessions + .iter() + .map(|(user_id, _, _)| *user_id) + .collect::>(); + let affected_iota_ids = previous_iota_ids + .iter() + .chain(iota_ids.iter()) + .copied() + .collect::>(); + let users = user_repo::get_users_by_ids_and_iota_ids( + &previous_session_user_ids + .iter() + .chain(new_session_user_ids.iter()) + .copied() + .collect::>(), + &affected_iota_ids.iter().copied().collect::>(), + ) + .await?; + let user_ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences( + &state, + user_repo::get_presence_preferences(&user_ids).await?, + ); + let before = states_for_users(&state, &users); + state + .presence + .replace_omikron_snapshot(omikron_id, &iota_ids, &sessions); + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(request_id)) + .await +} + +pub async fn omikron_disconnected(state: Arc, omikron_id: i64) { + let iota_ids = state.presence.iota_ids_owned_by(omikron_id); + let session_user_ids = state + .presence + .sessions_owned_by(omikron_id) + .into_iter() + .map(|(user_id, _, _)| user_id) + .collect::>(); + let users = match user_repo::get_users_by_ids_and_iota_ids(&session_user_ids, &iota_ids).await { + Ok(users) => users, + Err(error) => { + log_in!( + crate::util::logger::PrintType::General, + "Failed to load users before Omikron {} cleanup: {}", + omikron_id, + error + ); + return; + } + }; + let user_ids = users.iter().map(|user| user.id.0).collect::>(); + if let Err(error) = user_repo::get_presence_preferences(&user_ids) + .await + .map(|preferences| apply_preferences(&state, preferences)) + { + log_in!( + crate::util::logger::PrintType::General, + "Failed to load preferences before Omikron {} cleanup: {}", + omikron_id, + error + ); + return; + } + let before = states_for_users(&state, &users); + let removed = state.presence.remove_omikron(omikron_id); + debug_assert_eq!(removed.iota_ids, { + let mut ids = iota_ids.clone(); + ids.sort_unstable(); + ids.dedup(); + ids + }); + publish_changed_states(&state, &before, &users).await; +} + +#[cfg(test)] +mod tests { + use super::parse_subscription; + use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; + + fn request(user_ids: DataValue) -> CommunicationValue { + CommunicationValue::new(CommunicationType::StateSubscribe) + .with_sender(7) + .add_typed_default(DataType::SessionId, DataValue::SignedNumber(11)) + .add_typed_default(DataType::UserIds, user_ids) + } + + #[test] + fn subscription_parser_deduplicates_valid_targets() { + let parsed = parse_subscription(&request(DataValue::Array(vec![ + DataValue::SignedNumber(20), + DataValue::SignedNumber(21), + DataValue::SignedNumber(20), + ]))) + .unwrap(); + assert_eq!(parsed, (7, 11, vec![20, 21])); + } + + #[test] + fn subscription_parser_rejects_missing_or_malformed_fields() { + let missing_users = CommunicationValue::new(CommunicationType::StateSubscribe) + .with_sender(7) + .add_typed_default(DataType::SessionId, DataValue::SignedNumber(11)); + assert_eq!(parse_subscription(&missing_users), Err("user_ids")); + + let malformed_users = request(DataValue::Array(vec![DataValue::Str("bad".into())])); + assert_eq!(parse_subscription(&malformed_users), Err("user_ids")); + + let invalid_session = CommunicationValue::new(CommunicationType::StateSubscribe) + .with_sender(7) + .add_typed_default(DataType::SessionId, DataValue::SignedNumber(0)) + .add_typed_default( + DataType::UserIds, + DataValue::Array(vec![DataValue::SignedNumber(20)]), + ); + assert_eq!(parse_subscription(&invalid_session), Err("session_id")); + } + + #[test] + fn empty_subscription_is_valid_and_authoritative() { + let parsed = parse_subscription(&request(DataValue::Array(Vec::new()))).unwrap(); + assert_eq!(parsed, (7, 11, Vec::new())); } - Ok(()) } diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index be116b1..690a887 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -1,46 +1,146 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::sql::{connection_status::UserStatus, user_online_tracker}; +use crate::db::user_repo; use mtp::{ codec::{CommunicationType, CommunicationValue, DataType, DataValue}, type_map::TypeMap, }; -use std::sync::Arc; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +async fn send_error( + connection: Arc, + request_id: u32, + error_type: CommunicationType, + session_id: Option, +) -> OmikronResult<()> { + let mut response = CommunicationValue::new(error_type).with_id(request_id); + if let Some(session_id) = session_id { + response = + response.add_typed_default(DataType::SessionId, DataValue::SignedNumber(session_id)); + } + connection.send(&response).await +} pub async fn get( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let state = connection.state(); + let legacy_peer = !connection.peer_capabilities().client_state_push_v1; let DataValue::Array(ids) = value.get_data(DataType::UserIds) else { - return Ok(()); + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + None, + ) + .await; }; + let session_id = value + .get_data(DataType::SessionId) + .as_number() + .filter(|id| *id > 0); + if session_id.is_none() && !legacy_peer { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + None, + ) + .await; + } let tm = TypeMap::latest(); - let states = ids - .iter() - .filter_map(|id| { - let DataValue::SignedNumber(id) = id else { - return None; - }; - let status = user_online_tracker::get_user_status(*id as i64) - .map(|status| { - if status.connection_type == UserStatus::user_invisible { - UserStatus::user_offline.to_string() - } else { - status.connection_type.to_string() - } - }) - .unwrap_or_else(|| UserStatus::iota_offline.to_string()); - let mut map = Vec::new(); - if let Some(kind) = DataType::UserId.try_to_id(&tm) { - map.push((kind, DataValue::SignedNumber((*id as i64).into()))); - } - if let Some(kind) = DataType::UserState.try_to_id(&tm) { - map.push((kind, DataValue::Str(status))); - } - Some(DataValue::Container(map)) - }) - .collect(); + let mut requested_user_ids = Vec::new(); + let mut requested_set = HashSet::new(); + for id in ids { + let DataValue::SignedNumber(id) = id else { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + session_id, + ) + .await; + }; + let Ok(user_id) = i64::try_from(*id) else { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + session_id, + ) + .await; + }; + if user_id <= 0 { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + session_id, + ) + .await; + } + if !requested_set.insert(user_id) { + continue; + } + requested_user_ids.push(user_id); + } + + let users = match user_repo::get_users_by_ids(&requested_user_ids).await { + Ok(users) => users, + Err(_) => { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInternal, + session_id, + ) + .await; + } + }; + let users_by_id: HashMap<_, _> = users.into_iter().map(|user| (user.id.0, user)).collect(); + let mut states = Vec::new(); + let mut missing_user_ids = Vec::new(); + for user_id in requested_user_ids { + let Some(user) = users_by_id.get(&user_id) else { + missing_user_ids.push(user_id); + continue; + }; + let status = state + .presence + .resolve_public_state(user_id, user.iota_id.0) + .to_string(); + let mut map = Vec::new(); + if let Some(kind) = DataType::UserId.try_to_id(&tm) { + map.push((kind, DataValue::SignedNumber(user_id.into()))); + } + if let Some(kind) = DataType::UserState.try_to_id(&tm) { + map.push((kind, DataValue::Str(status))); + } + states.push(DataValue::Container(map)); + } let response = CommunicationValue::new(CommunicationType::GetStates) .with_id(value.get_id()) .add_typed_default(DataType::UserStates, DataValue::Array(states)); + let response = if let Some(session_id) = session_id { + response.add_typed_default(DataType::SessionId, DataValue::SignedNumber(session_id)) + } else { + response + }; + let response = if legacy_peer { + response + } else { + response.add_typed_default( + DataType::MissingUserIds, + DataValue::Array( + missing_user_ids + .into_iter() + .map(|id| DataValue::SignedNumber(id.into())) + .collect(), + ), + ) + }; connection.send(&response).await } diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index 27c9dbb..7e79bee 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -2,7 +2,6 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, - sql::{connection_status::UserStatus, user_online_tracker}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use mtp::{ @@ -11,9 +10,12 @@ use mtp::{ }; use std::sync::Arc; -fn connections(iota_id: i64) -> DataValue { +fn connections(connection: &OmikronConnection, iota_id: i64) -> DataValue { DataValue::Array( - user_online_tracker::get_iota_omikron_connections(iota_id) + connection + .state() + .presence + .iota_connections(iota_id) .unwrap_or_default() .into_iter() .map(|id| DataValue::SignedNumber(id.into())) @@ -25,6 +27,7 @@ pub async fn get_user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let state = connection.state(); let user = if let Some(id) = value.get_data(DataType::UserId).as_number() { user_repo::get_by_user_id(UserId::from(id as i64)) .await @@ -74,28 +77,36 @@ pub async fn get_user( response = response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar))); } - let online = user_online_tracker::get_user_status(id); + let route = state.presence.user_route(id); + let private_request = value.get_sender() as i64 == id; + let resolved_status = if private_request { + if !state + .presence + .load_preference(id, &user.presence_preference) + { + crate::log_in!( + crate::util::logger::PrintType::General, + "Invalid persisted presence preference for user {}, using user_online", + id + ); + } + state.presence.resolve_private_state(id) + } else { + state.presence.resolve_public_state(id, iota_id) + }; response = response .add_typed_default( DataType::OnlineStatus, - DataValue::Str( - online - .as_ref() - .map(|status| { - if status.connection_type == UserStatus::user_invisible { - UserStatus::user_offline.to_string() - } else { - status.connection_type.to_string() - } - }) - .unwrap_or_else(|| UserStatus::iota_offline.to_string()), - ), + DataValue::Str(resolved_status.to_string()), ) - .add_typed_default(DataType::OmikronConnections, connections(iota_id)); - if let Some(status) = online { + .add_typed_default( + DataType::OmikronConnections, + connections(&connection, iota_id), + ); + if let Some(route) = route { response = response.add_typed_default( DataType::OmikronId, - DataValue::SignedNumber(status.omikron_id.into()), + DataValue::SignedNumber(route.omikron_id.into()), ); } connection.send(&response).await @@ -147,7 +158,7 @@ pub async fn get_iota( .with_id(value.get_id()) .add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::OmikronConnections, connections(id)); + .add_typed_default(DataType::OmikronConnections, connections(&connection, id)); if let Some(user_id) = user_id { response = response.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())); diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 8171bf2..52c5fe8 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,3 +1,4 @@ +pub mod capabilities; pub mod connection; pub mod handlers; pub mod omikron_connection; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index e86f4a2..5001ff3 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,6 +1,8 @@ +use super::capabilities::{OmegaCapabilities, PeerCapabilities}; use crate::models::OmikronId; use crate::{ load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server, + state::OmegaState, transport::omikron_manager, util::{file_util::load_file_vec, logger::PrintType}, }; @@ -48,9 +50,11 @@ pub struct WaitingTask { pub struct OmikronConnection { id: u64, + state: Arc, sender: Mutex>, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, + peer_capabilities: PeerCapabilities, } impl Drop for OmikronConnection { fn drop(&mut self) { @@ -61,13 +65,26 @@ impl Drop for OmikronConnection { } impl OmikronConnection { - pub fn new(sender: WebMtpSender, id: u64) -> Arc { - Arc::new(Self { + pub fn new( + sender: WebMtpSender, + id: u64, + description: Option<&str>, + state: Arc, + ) -> Option> { + let peer_capabilities = + PeerCapabilities::from_identification_description(description).ok()?; + Some(Arc::new(Self { id, + state, sender: Mutex::new(Some(sender)), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), - }) + peer_capabilities, + })) + } + + pub fn peer_capabilities(&self) -> &PeerCapabilities { + &self.peer_capabilities } pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { @@ -76,6 +93,23 @@ impl OmikronConnection { PrintType::Omega, "Omikron connection started" ); + let capabilities = CommunicationValue::new(CommunicationType::IdentificationResponse) + .add_typed_default( + mtp::codec::DataType::Description, + mtp::codec::DataValue::Str( + OmegaCapabilities::current().identification_description(), + ), + ); + if let Err(error) = self.clone().send(&capabilities).await { + log_err!( + self.id as i64, + PrintType::Omega, + "Failed to send Omega capabilities: {}", + error + ); + self.clone().cleanup().await; + return; + } let cleanup_conn = self.clone(); *self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); @@ -132,27 +166,30 @@ impl OmikronConnection { async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { let id = self.id as i64; + let state = self.state.clone(); match value.get_comm_type_enum() { Some(CommunicationType::ShortenLink) => { crate::transport::handlers::links::shorten(self, value).await } Some(CommunicationType::UserConnected) => { - crate::transport::handlers::presence::user_connected(self, value, id).await + crate::transport::handlers::presence::user_connected(state, self, value, id).await } Some(CommunicationType::UserDisconnected) => { - crate::transport::handlers::presence::user_disconnected(self, value, id).await + crate::transport::handlers::presence::user_disconnected(state, self, value, id) + .await } - Some(CommunicationType::ClientChanged) => { - crate::transport::handlers::presence::client_changed(self, value, id).await + Some(CommunicationType::SetUserState) => { + crate::transport::handlers::presence::set_user_state(state, self, value, id).await } Some(CommunicationType::IotaConnected) => { - crate::transport::handlers::presence::iota_connected(self, value, id).await + crate::transport::handlers::presence::iota_connected(state, self, value, id).await } Some(CommunicationType::IotaDisconnected) => { - crate::transport::handlers::presence::iota_disconnected(self, value, id).await + crate::transport::handlers::presence::iota_disconnected(state, self, value, id) + .await } Some(CommunicationType::SyncClientIotaStatus) => { - crate::transport::handlers::presence::sync_status(self, value, id).await + crate::transport::handlers::presence::sync_status(state, self, value, id).await } Some(CommunicationType::GetUserData) => { crate::transport::handlers::user_data::get_user(self, value).await @@ -193,6 +230,13 @@ impl OmikronConnection { Some(CommunicationType::GetStates) => { crate::transport::handlers::states::get(self, value).await } + Some(CommunicationType::StateSubscribe) => { + crate::transport::handlers::presence::state_subscribe(state, self, value, id).await + } + Some(CommunicationType::ClientChanged) => { + crate::transport::handlers::presence::client_changed_legacy(state, self, value, id) + .await + } _ => { log_err!( 0, @@ -216,6 +260,24 @@ impl OmikronConnection { .await .map_err(|error| crate::error::OmegaError::SendError(error.to_string())) } + + pub(crate) async fn send_messages( + self: Arc, + values: &[CommunicationValue], + ) -> OmikronResult<()> { + let guard = self.sender.lock().await; + let sender = guard + .as_ref() + .ok_or(crate::error::OmegaError::NotConnected)?; + for value in values { + log_cv_out!(PrintType::Omikron, value); + sender + .send(value) + .await + .map_err(|error| crate::error::OmegaError::SendError(error.to_string()))?; + } + Ok(()) + } pub(crate) async fn send_error_response( self: Arc, message_id: u32, @@ -224,6 +286,22 @@ impl OmikronConnection { self.send(&CommunicationValue::new(error_type).with_id(message_id)) .await } + pub(crate) async fn send_error_response_with_detail( + self: Arc, + message_id: u32, + error_type: CommunicationType, + detail: &'static str, + ) -> OmikronResult<()> { + self.send( + &CommunicationValue::new(error_type) + .with_id(message_id) + .add_typed_default( + mtp::codec::DataType::ErrorType, + mtp::codec::DataValue::Str(detail.to_string()), + ), + ) + .await + } pub async fn close(self: Arc) { log_in!( self.id as i64, @@ -238,7 +316,11 @@ impl OmikronConnection { if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); if omikron_manager::remove_omikron(self.id as i64, &self).await { - crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; + crate::transport::handlers::presence::omikron_disconnected( + self.state.clone(), + self.id as i64, + ) + .await; } } if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { @@ -248,12 +330,19 @@ impl OmikronConnection { pub async fn get_omikron_id(self: Arc) -> Option { Some(self.id as i64) } + pub fn state(&self) -> Arc { + self.state.clone() + } pub async fn send_message(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { self.send(value).await } } -pub async fn get_by_omikron_id(omikron_id: u64, _: Option) -> Option { +pub async fn get_by_omikron_id( + omikron_id: u64, + description: Option, +) -> Option { + PeerCapabilities::from_identification_description(description.as_deref()).ok()?; crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64)) .await .ok() @@ -263,7 +352,7 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { 0 } -pub async fn start(port: u16) -> Result<(), Box> { +pub async fn start(port: u16, state: Arc) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "cert.pem")?; let key_pem = load_file_vec("certs", "key.pem")?; let web_config = server::server::build_web_config()? @@ -331,7 +420,19 @@ pub async fn start(port: u16) -> Result<(), Box> { ); continue; } - let connection = OmikronConnection::new(conn.sender, conn.client_id); + let Some(connection) = OmikronConnection::new( + conn.sender, + conn.client_id, + conn.description.as_deref(), + state.clone(), + ) else { + log_err!( + 0, + PrintType::Omega, + "Rejected Omikron connection with invalid capabilities" + ); + continue; + }; tokio::spawn(async move { let _guard = ConnectionLimitGuard(peer_ip); omikron_manager::add_omikron(connection.clone()).await; diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 0e1575d..254ced8 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,4 +1,7 @@ +use crate::db::user_repo; +use crate::state::OmegaState; use crate::transport::connection::OmikronConnection; +use crate::transport::omikron_connection::OmikronResult; use dashmap::DashMap; use mtp::codec::CommunicationValue; use once_cell::sync::Lazy; @@ -34,6 +37,61 @@ pub fn get_connected_omikron(omikron_id: i64) -> Option> .map(|connection| connection.clone()) } +pub fn get_state() -> Option> { + OMIKRON_CONNECTIONS + .iter() + .next() + .map(|connection| connection.value().state()) +} + +pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { + get_state().and_then(|state| state.presence.primary_iota_route(iota_id)) +} + +pub async fn get_all_connections() +-> Result>>, ()> { + match get_state() { + Some(state) => { + let mut result = state.presence.connection_routes(); + let iota_ids = state + .presence + .all_iota_routes() + .keys() + .copied() + .collect::>(); + let users = user_repo::get_users_by_iota_ids(&iota_ids) + .await + .map_err(|_| ())?; + for user in users { + for route in state.presence.routes_for_user(user.id.0) { + if let Some(iotas) = result.get_mut(&route.omikron_id) { + if let Some(users) = iotas.get_mut(&user.iota_id.0) { + users.push(user.id.0); + } + } + } + } + for iotas in result.values_mut() { + for users in iotas.values_mut() { + users.sort_unstable(); + users.dedup(); + } + } + Ok(result) + } + None => Ok(std::collections::HashMap::new()), + } +} + +pub async fn send_state_batch( + omikron_id: i64, + notifications: Vec, +) -> OmikronResult<()> { + let connection = + get_connected_omikron(omikron_id).ok_or(crate::error::OmegaError::NotConnected)?; + connection.send_messages(¬ifications).await +} + pub async fn get_random_omikron() -> Result, ()> { let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); @@ -47,9 +105,11 @@ pub async fn get_random_omikron() -> Result, ()> { } pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) { - if let Some(user_conn) = crate::sql::user_online_tracker::get_user_status(user_id) { - if let Some(omikron_conn) = OMIKRON_CONNECTIONS.get(&user_conn.omikron_id) { - let _ = omikron_conn.value().clone().send_message(cv).await; + if let Some(state) = get_state() { + for user_route in state.presence.routes_for_user(user_id) { + if let Some(omikron_conn) = OMIKRON_CONNECTIONS.get(&user_route.omikron_id) { + let _ = omikron_conn.value().clone().send_message(cv).await; + } } } } From c447e6e8639e51a02ffcfe78fb7057e845306aec Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 7 Aug 2026 23:54:34 +0200 Subject: [PATCH 210/220] [Fix] User States --- migrations/004_presence_preference.sql | 22 + mtp-type-maps | 2 +- src/db/user_repo.rs | 143 ++- src/main.rs | 4 +- src/models/user.rs | 1 + src/server/api.rs | 6 +- src/sql/connection_status.rs | 75 +- src/sql/user_online_tracker.rs | 1277 ++++++++++++++++++++---- src/state.rs | 72 ++ src/transport/capabilities.rs | 147 +++ src/transport/handlers/presence.rs | 862 ++++++++++++++-- src/transport/handlers/states.rs | 156 ++- src/transport/handlers/user_data.rs | 51 +- src/transport/mod.rs | 1 + src/transport/omikron_connection.rs | 129 ++- src/transport/omikron_manager.rs | 66 +- 16 files changed, 2613 insertions(+), 401 deletions(-) create mode 100644 migrations/004_presence_preference.sql create mode 100644 src/state.rs create mode 100644 src/transport/capabilities.rs diff --git a/migrations/004_presence_preference.sql b/migrations/004_presence_preference.sql new file mode 100644 index 0000000..163a623 --- /dev/null +++ b/migrations/004_presence_preference.sql @@ -0,0 +1,22 @@ +ALTER TABLE users + ADD COLUMN presence_preference VARBINARY(32) NOT NULL DEFAULT 'user_online'; + +UPDATE users +SET presence_preference = 'user_online' +WHERE presence_preference NOT IN ( + 'user_online', + 'user_idle', + 'user_dnd', + 'user_wc', + 'user_invisible' +); + +ALTER TABLE users + ADD CONSTRAINT chk_users_presence_preference + CHECK (presence_preference IN ( + 'user_online', + 'user_idle', + 'user_dnd', + 'user_wc', + 'user_invisible' + )); diff --git a/mtp-type-maps b/mtp-type-maps index ece6e2c..486541b 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit ece6e2c3b4e925f3cefe46f4a048fbfc8f823093 +Subproject commit 486541b9483356ff49ff3ec7016f87d3ecbeaa0e diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index 8554566..01b0e6e 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -2,9 +2,11 @@ use crate::{ db::pool, error::{OmegaError, Result}, models::{IotaId, User, UserId}, + sql::connection_status::UserStatus, }; use mtp::crypto::PublicKeyBundle; -use sqlx::{FromRow, Row}; +use sqlx::{FromRow, MySql, QueryBuilder, Row}; +use std::collections::HashMap; pub const MAX_PROTOCOL_ID: i64 = (1_i64 << 48) - 1; const ID_ALLOCATION_ATTEMPTS: usize = 16; @@ -92,9 +94,9 @@ pub(crate) fn is_duplicate_key(error: &sqlx::Error) -> bool { }) } -const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; -const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; -const USERS_BY_IOTA_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE iota_id = ?"; +const USER_BY_USERNAME_QUERY: &str = "SELECT id, iota_id, username, display, status, presence_preference, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE username = ?"; +const USER_BY_ID_QUERY: &str = "SELECT id, iota_id, username, display, status, presence_preference, about, avatar, sub_level, sub_end, public_key, token FROM users WHERE id = ?"; +const USER_COLUMNS: &str = "SELECT id, iota_id, username, display, status, presence_preference, about, avatar, sub_level, sub_end, public_key, token FROM users"; #[derive(FromRow)] struct UserRow { @@ -103,6 +105,7 @@ struct UserRow { username: Vec, display: Option>, status: Option>, + presence_preference: Vec, about: Option>, avatar: Option>, sub_level: i32, @@ -125,6 +128,7 @@ impl TryFrom for User { username: decode(row.username)?, display: row.display.map(decode).transpose()?, status: row.status.map(decode).transpose()?, + presence_preference: decode(row.presence_preference)?, about: row.about.map(decode).transpose()?, avatar: row.avatar, sub_level: row.sub_level, @@ -154,15 +158,129 @@ pub async fn get_by_user_id(id: UserId) -> Result { } pub async fn get_users_by_iota_id(id: IotaId) -> Result> { - let rows = sqlx::query_as::<_, UserRow>(USERS_BY_IOTA_ID_QUERY) - .bind(id.0) + get_users_by_iota_ids(&[id.0]).await +} + +fn normalized_ids(ids: &[i64]) -> Vec { + let mut ids = ids + .iter() + .copied() + .filter(|id| valid_protocol_id(*id)) + .collect::>(); + ids.sort_unstable(); + ids.dedup(); + ids +} + +fn append_in_clause(query: &mut QueryBuilder<'_, MySql>, ids: &[i64]) { + query.push("("); + for (index, id) in ids.iter().enumerate() { + if index > 0 { + query.push(", "); + } + query.push_bind(*id); + } + query.push(")"); +} + +async fn fetch_users(mut query: QueryBuilder<'_, MySql>) -> Result> { + query + .build_query_as::() .fetch_all(&pool().await?) - .await?; - rows.into_iter() + .await? + .into_iter() .map(|row| row.try_into().map_err(OmegaError::from)) .collect() } +pub async fn get_users_by_ids(ids: &[i64]) -> Result> { + let ids = normalized_ids(ids); + if ids.is_empty() { + return Ok(Vec::new()); + } + let mut query = QueryBuilder::::new(USER_COLUMNS); + query.push(" WHERE id IN "); + append_in_clause(&mut query, &ids); + fetch_users(query).await +} + +pub async fn get_users_by_iota_ids(ids: &[i64]) -> Result> { + let ids = normalized_ids(ids); + if ids.is_empty() { + return Ok(Vec::new()); + } + let mut query = QueryBuilder::::new(USER_COLUMNS); + query.push(" WHERE iota_id IN "); + append_in_clause(&mut query, &ids); + fetch_users(query).await +} + +pub async fn get_users_by_ids_and_iota_ids( + user_ids: &[i64], + iota_ids: &[i64], +) -> Result> { + let user_ids = normalized_ids(user_ids); + let iota_ids = normalized_ids(iota_ids); + if user_ids.is_empty() && iota_ids.is_empty() { + return Ok(Vec::new()); + } + + let mut query = QueryBuilder::::new(USER_COLUMNS); + query.push(" WHERE "); + if !user_ids.is_empty() { + query.push("id IN "); + append_in_clause(&mut query, &user_ids); + } + if !iota_ids.is_empty() { + if !user_ids.is_empty() { + query.push(" OR "); + } + query.push("iota_id IN "); + append_in_clause(&mut query, &iota_ids); + } + fetch_users(query).await +} + +#[derive(FromRow)] +struct PresencePreferenceRow { + id: i64, + presence_preference: Vec, +} + +pub async fn get_presence_preferences(ids: &[i64]) -> Result> { + let ids = normalized_ids(ids); + if ids.is_empty() { + return Ok(HashMap::new()); + } + + let mut query = + QueryBuilder::::new("SELECT id, presence_preference FROM users WHERE id IN "); + append_in_clause(&mut query, &ids); + let rows = query + .build_query_as::() + .fetch_all(&pool().await?) + .await?; + let mut preferences = HashMap::with_capacity(rows.len()); + for row in rows { + let status = String::from_utf8(row.presence_preference) + .ok() + .and_then(|value| UserStatus::from_client_preference(&value)); + let status = match status { + Some(status) => status, + None => { + crate::log_in!( + crate::util::logger::PrintType::General, + "Invalid persisted presence preference for user {}, using user_online", + row.id + ); + UserStatus::user_online + } + }; + preferences.insert(row.id, status); + } + Ok(preferences) +} + async fn update( id: UserId, query: &'static str, @@ -217,6 +335,15 @@ pub async fn change_status(id: UserId, value: String) -> Result<()> { .await } +pub async fn change_presence_preference(id: UserId, value: String) -> Result<()> { + update( + id, + "UPDATE users SET presence_preference = ? WHERE id = ?", + value.into_bytes(), + ) + .await +} + pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> { sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") .bind(value.0) diff --git a/src/main.rs b/src/main.rs index 251713e..16b850e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,12 +5,14 @@ pub mod error; mod models; mod server; mod sql; +mod state; mod transport; mod util; pub use error::{OmegaError, Result}; use crate::db::initialize; +use crate::state::OmegaState; use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; @@ -94,7 +96,7 @@ async fn main() { .unwrap_or(443); tokio::select! { - result = omikron_connection::start(port) => { + result = omikron_connection::start(port, OmegaState::new()) => { if let Err(e) = result { log_err!(0, PrintType::General, "Server error: {:?}", e); } diff --git a/src/models/user.rs b/src/models/user.rs index 0dda84c..2843b6d 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -8,6 +8,7 @@ pub struct User { pub username: String, pub display: Option, pub status: Option, + pub presence_preference: String, pub about: Option, pub avatar: Option>, pub sub_level: i32, diff --git a/src/server/api.rs b/src/server/api.rs index ed5bb80..498685a 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -14,8 +14,10 @@ use crate::server::{ middleware, validation::{parse_positive_id, validate_non_empty}, }; -use crate::sql::user_online_tracker::{get_all_connections, get_iota_primary_omikron_connection}; -use crate::transport::omikron_manager::{get_connected_omikron, get_random_omikron}; +use crate::transport::omikron_manager::{ + get_all_connections, get_connected_omikron, get_iota_primary_omikron_connection, + get_random_omikron, +}; use crate::util::file_util::get_directory; use base64::Engine as _; use bytes::Bytes; diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 8587ffe..104e316 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -1,7 +1,4 @@ -use strum::IntoEnumIterator; -use strum_macros::EnumIter; - -#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] #[allow(unused, non_camel_case_types)] pub enum UserStatus { user_offline, @@ -20,12 +17,70 @@ impl UserStatus { pub fn to_string(&self) -> String { format!("{:?}", self) } - pub fn from_str(s: &str) -> Option { - for sel in UserStatus::iter() { - if &sel.to_string() == s { - return Some(sel); - } + pub fn from_client_preference(s: &str) -> Option { + match s { + "user_online" => Some(Self::user_online), + "user_idle" => Some(Self::user_idle), + "user_dnd" => Some(Self::user_dnd), + "user_wc" => Some(Self::user_wc), + "user_invisible" => Some(Self::user_invisible), + _ => None, } - None + } + + pub fn public_value(&self) -> Self { + match self { + Self::user_invisible => Self::user_offline, + value => value.clone(), + } + } + + /// Parse a value received from a client or persisted as an account + /// preference. Derived connectivity and diagnostic states are never valid + /// preferences. + pub fn from_str(s: &str) -> Option { + Self::from_client_preference(s) + } +} + +#[cfg(test)] +mod tests { + use super::UserStatus; + + #[test] + fn accepts_only_client_preferences() { + for value in [ + "user_online", + "user_idle", + "user_dnd", + "user_wc", + "user_invisible", + ] { + assert!( + UserStatus::from_client_preference(value).is_some(), + "{value}" + ); + } + + for value in [ + "user_offline", + "iota_offline", + "iota_online", + "user_borked", + "iota_borked", + "unknown", + ] { + assert_eq!(UserStatus::from_client_preference(value), None, "{value}"); + assert_eq!(UserStatus::from_str(value), None, "{value}"); + } + } + + #[test] + fn invisible_is_publicly_offline() { + assert_eq!( + UserStatus::user_invisible.public_value(), + UserStatus::user_offline + ); + assert_eq!(UserStatus::user_dnd.public_value(), UserStatus::user_dnd); } } diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index b0e5f91..8017c3d 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1,231 +1,1094 @@ -use crate::db::user_repo; -use crate::models::IotaId; use crate::sql::connection_status::UserStatus; -use dashmap::DashMap; -use once_cell::sync::Lazy; +use std::collections::{HashMap, HashSet}; +use std::sync::RwLock; -#[derive(Debug, Clone)] -pub struct UserConnection { - pub connection_type: UserStatus, +fn select_primary_route(previous_primary: Option, connected: &HashSet) -> Option { + previous_primary + .filter(|id| connected.contains(id)) + .or_else(|| connected.iter().min().copied()) +} + +fn sorted_vec(values: HashSet) -> Vec { + let mut values = values.into_iter().collect::>(); + values.sort_unstable(); + values +} + +fn sorted_pairs(values: HashSet<(i64, i64)>) -> Vec<(i64, i64)> { + let mut values = values.into_iter().collect::>(); + values.sort_unstable(); + values +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ClientSessionRoute { + pub omikron_id: i64, + pub iota_id: i64, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub struct PresenceSubscriber { + pub user_id: i64, + pub session_id: i64, pub omikron_id: i64, } -// IotaID -> Primary OmikronID -static IOTA_PRIMARY_OMIKRON_CONNECTION: Lazy> = Lazy::new(DashMap::new); - -// IotaID -> Vec -static IOTA_OMIKRON_CONNECTIONS: Lazy>> = Lazy::new(DashMap::new); - -// UserID -> UserStatus -static USER_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); - -// The legacy account map is kept for Iota-only/offline compatibility. Client -// transports are tracked independently: two devices must never overwrite each -// other's route merely because they authenticate as the same account. -static USER_SESSION_STATUS_MAP: Lazy> = Lazy::new(DashMap::new); - -pub fn track_iota_connection(iota_id: i64, omikron_id: i64, primary: bool) { - let mut entry = IOTA_OMIKRON_CONNECTIONS - .entry(iota_id) - .or_insert_with(Vec::new); - - if !entry.contains(&omikron_id) { - entry.push(omikron_id); - } - - if primary { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PresenceIndices { + pub sessions_by_user: HashMap>, + pub sessions_by_omikron: HashMap>, + pub targets_by_subscriber: HashMap<(i64, i64), HashSet>, + pub subscribers_by_omikron: HashMap>, + pub iotas_by_omikron: HashMap>, } -pub fn untrack_iota_connection(iota_id: i64, omikron_id: i64) -> bool { - let mut replacement = None; - let mut connections_empty = false; - if let Some(mut entry) = IOTA_OMIKRON_CONNECTIONS.get_mut(&iota_id) { - entry.retain(|&id| id != omikron_id); - connections_empty = entry.is_empty(); - replacement = entry.first().copied(); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct IotaRoute { + pub primary_omikron_id: Option, + pub connected_omikron_ids: HashSet, +} - if connections_empty { - IOTA_OMIKRON_CONNECTIONS.remove_if(&iota_id, |_, connections| connections.is_empty()); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PresenceRoutes { + pub iotas: HashMap, + pub sessions: HashMap<(i64, i64), ClientSessionRoute>, + pub subscriptions: HashMap>, + pub indices: PresenceIndices, +} - if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .is_some_and(|primary| *primary == omikron_id) - { - match replacement { - Some(omikron_id) => { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, omikron_id); - } - None => { - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(&iota_id); - } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct RemovedOmikronState { + pub iota_ids: Vec, + pub session_user_ids: Vec, + pub subscriber_sessions: Vec<(i64, i64)>, +} + +/// All live presence state is kept together so routing cannot be overwritten +/// by a preference update. Preferences are account-scoped, routes are +/// session-scoped, and effective state is always derived. +pub struct PresenceTracker { + pub(crate) routes: RwLock, + preferences: RwLock>, +} + +impl Default for PresenceTracker { + fn default() -> Self { + Self { + routes: RwLock::new(PresenceRoutes::default()), + preferences: RwLock::new(HashMap::new()), } } - - connections_empty } -pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { - IOTA_PRIMARY_OMIKRON_CONNECTION.get(&iota_id).map(|v| *v) -} - -pub fn get_iota_omikron_connections(iota_id: i64) -> Option> { - IOTA_OMIKRON_CONNECTIONS.get(&iota_id).map(|v| v.clone()) -} - -pub async fn get_all_connections() --> Result>>, ()> { - let mut result: std::collections::HashMap>> = - std::collections::HashMap::new(); - - for entry in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *entry.key(); - for omikron_id in entry.value().iter() { - result - .entry(*omikron_id) - .or_insert_with(std::collections::HashMap::new) - .entry(iota_id) - .or_insert_with(Vec::new); - } - } - - for entry in IOTA_OMIKRON_CONNECTIONS.iter() { - let iota_id = *entry.key(); - let omikron_ids = entry.value().clone(); - - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - for user in users { - let user_id = user.id.0; - if let Some(conn) = USER_STATUS_MAP.get(&user_id) { - let user_omikron_id = conn.omikron_id; - if omikron_ids.contains(&user_omikron_id) { - if let Some(iota_map) = result.get_mut(&user_omikron_id) { - if let Some(user_vec) = iota_map.get_mut(&iota_id) { - user_vec.push(user_id); - } - } +impl PresenceTracker { + fn remove_subscription_locked(routes: &mut PresenceRoutes, key: (i64, i64)) { + let Some(targets) = routes.indices.targets_by_subscriber.remove(&key) else { + return; + }; + let mut omikron_ids = HashSet::new(); + for target_user_id in targets { + if let Some(subscribers) = routes.subscriptions.get_mut(&target_user_id) { + subscribers.retain(|subscriber| { + if (subscriber.user_id, subscriber.session_id) == key { + omikron_ids.insert(subscriber.omikron_id); + false + } else { + true } + }); + if subscribers.is_empty() { + routes.subscriptions.remove(&target_user_id); + } + } + } + for omikron_id in omikron_ids { + if let Some(sessions) = routes.indices.subscribers_by_omikron.get_mut(&omikron_id) { + sessions.remove(&key); + if sessions.is_empty() { + routes.indices.subscribers_by_omikron.remove(&omikron_id); } } } } - Ok(result) -} - -pub fn track_user_status(user_id: i64, status: UserStatus, omikron_id: i64) { - USER_STATUS_MAP.insert( - user_id, - UserConnection { - connection_type: status, - omikron_id, - }, - ); -} - -pub fn track_user_session_status( - user_id: i64, - session_id: i64, - status: UserStatus, - omikron_id: i64, -) { - USER_SESSION_STATUS_MAP.insert( - (user_id, session_id), - UserConnection { - connection_type: status, - omikron_id, - }, - ); -} - -pub fn untrack_user_status(user_id: i64, omikron_id: i64) { - USER_STATUS_MAP.remove_if(&user_id, |_, connection| { - connection.omikron_id == omikron_id - }); -} - -pub fn untrack_user_session_status(user_id: i64, session_id: i64, omikron_id: i64) { - USER_SESSION_STATUS_MAP.remove_if(&(user_id, session_id), |_, connection| { - connection.omikron_id == omikron_id - }); -} - -pub fn update_user_session_status(user_id: i64, status: UserStatus) { - for mut entry in USER_SESSION_STATUS_MAP.iter_mut() { - if entry.key().0 == user_id { - entry.connection_type = status.clone(); + fn remove_session_locked( + routes: &mut PresenceRoutes, + key: (i64, i64), + expected_omikron_id: Option, + ) -> bool { + let Some(route) = routes.sessions.get(&key) else { + return false; + }; + if expected_omikron_id.is_some_and(|id| route.omikron_id != id) { + return false; } - } - // Preserve the account preference for legacy routes as well. - if let Some(mut entry) = USER_STATUS_MAP.get_mut(&user_id) { - entry.connection_type = status; - } -} - -pub fn get_user_status(user_id: i64) -> Option { - // A connected visible session is preferred. Invisible sessions remain - // routable but are intentionally presented as offline when they are the - // only active routes. - let sessions: Vec = USER_SESSION_STATUS_MAP - .iter() - .filter(|entry| entry.key().0 == user_id) - .map(|entry| entry.value().clone()) - .collect(); - if let Some(status) = sessions - .iter() - .find(|status| status.connection_type != UserStatus::user_invisible) - { - return Some(status.clone()); - } - if let Some(status) = sessions.first() { - return Some(status.clone()); - } - USER_STATUS_MAP.get(&user_id).map(|v| v.clone()) -} - -pub fn untrack_many_users(user_ids: &[i64]) { - for user_id in user_ids { - USER_STATUS_MAP.remove(user_id); - } -} - -pub async fn untrack_omikron(omikron_id: i64) { - let mut offline_iotas = Vec::new(); - let mut primary_replacements = Vec::new(); - - for mut entry in IOTA_OMIKRON_CONNECTIONS.iter_mut() { - let iota_id = *entry.key(); - entry.retain(|&id| id != omikron_id); - if entry.is_empty() { - offline_iotas.push(iota_id); - } else if IOTA_PRIMARY_OMIKRON_CONNECTION - .get(&iota_id) - .is_some_and(|primary| *primary == omikron_id) - { - primary_replacements.push((iota_id, entry[0])); - } - } - - for iota_id in &offline_iotas { - IOTA_OMIKRON_CONNECTIONS.remove_if(iota_id, |_, connections| connections.is_empty()); - IOTA_PRIMARY_OMIKRON_CONNECTION.remove(iota_id); - } - - for (iota_id, replacement) in primary_replacements { - IOTA_PRIMARY_OMIKRON_CONNECTION.insert(iota_id, replacement); - } - - USER_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - USER_SESSION_STATUS_MAP.retain(|_, status| status.omikron_id != omikron_id); - - for iota_id in offline_iotas { - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - for user in users { - USER_STATUS_MAP.remove(&user.id.0); + let route = routes.sessions.remove(&key).unwrap(); + if let Some(session_ids) = routes.indices.sessions_by_user.get_mut(&key.0) { + session_ids.remove(&key.1); + if session_ids.is_empty() { + routes.indices.sessions_by_user.remove(&key.0); } } + if let Some(session_keys) = routes + .indices + .sessions_by_omikron + .get_mut(&route.omikron_id) + { + session_keys.remove(&key); + if session_keys.is_empty() { + routes.indices.sessions_by_omikron.remove(&route.omikron_id); + } + } + Self::remove_subscription_locked(routes, key); + true + } + + fn insert_session_locked( + routes: &mut PresenceRoutes, + key: (i64, i64), + route: ClientSessionRoute, + ) { + Self::remove_session_locked(routes, key, None); + routes.sessions.insert(key, route.clone()); + routes + .indices + .sessions_by_user + .entry(key.0) + .or_default() + .insert(key.1); + routes + .indices + .sessions_by_omikron + .entry(route.omikron_id) + .or_default() + .insert(key); + } + + fn remove_iota_owner_locked( + routes: &mut PresenceRoutes, + iota_id: i64, + omikron_id: i64, + ) -> bool { + let Some(route) = routes.iotas.get_mut(&iota_id) else { + return false; + }; + route.connected_omikron_ids.remove(&omikron_id); + route.primary_omikron_id = + select_primary_route(route.primary_omikron_id, &route.connected_omikron_ids); + let empty = route.connected_omikron_ids.is_empty(); + if let Some(iotas) = routes.indices.iotas_by_omikron.get_mut(&omikron_id) { + iotas.remove(&iota_id); + if iotas.is_empty() { + routes.indices.iotas_by_omikron.remove(&omikron_id); + } + } + if empty { + routes.iotas.remove(&iota_id); + } + empty + } + + #[cfg(debug_assertions)] + fn debug_assert_indices(routes: &PresenceRoutes) { + debug_assert!(Self::check_indices(routes).is_ok()); + } + + #[cfg(not(debug_assertions))] + fn debug_assert_indices(_: &PresenceRoutes) {} + + fn check_indices(routes: &PresenceRoutes) -> Result<(), String> { + for (key, route) in &routes.sessions { + if !routes + .indices + .sessions_by_user + .get(&key.0) + .is_some_and(|sessions| sessions.contains(&key.1)) + { + return Err(format!("session {key:?} missing user index")); + } + if !routes + .indices + .sessions_by_omikron + .get(&route.omikron_id) + .is_some_and(|sessions| sessions.contains(key)) + { + return Err(format!("session {key:?} missing Omikron index")); + } + } + for (user_id, session_ids) in &routes.indices.sessions_by_user { + for session_id in session_ids { + if !routes.sessions.contains_key(&(*user_id, *session_id)) { + return Err(format!("stale user session index {user_id}:{session_id}")); + } + } + } + for (omikron_id, session_keys) in &routes.indices.sessions_by_omikron { + for key in session_keys { + if routes.sessions.get(key).map(|route| route.omikron_id) != Some(*omikron_id) { + return Err(format!("stale Omikron session index {omikron_id}:{key:?}")); + } + } + } + for (target_user_id, subscribers) in &routes.subscriptions { + for subscriber in subscribers { + let key = (subscriber.user_id, subscriber.session_id); + if !routes + .indices + .targets_by_subscriber + .get(&key) + .is_some_and(|targets| targets.contains(target_user_id)) + { + return Err(format!("subscription {key:?} missing target index")); + } + if !routes + .indices + .subscribers_by_omikron + .get(&subscriber.omikron_id) + .is_some_and(|sessions| sessions.contains(&key)) + { + return Err(format!("subscription {key:?} missing Omikron index")); + } + } + } + for (key, targets) in &routes.indices.targets_by_subscriber { + for target in targets { + if !routes.subscriptions.get(target).is_some_and(|subscribers| { + subscribers + .iter() + .any(|subscriber| (subscriber.user_id, subscriber.session_id) == *key) + }) { + return Err(format!("stale target index {key:?}:{target}")); + } + } + } + for (omikron_id, iota_ids) in &routes.indices.iotas_by_omikron { + for iota_id in iota_ids { + if !routes + .iotas + .get(iota_id) + .is_some_and(|route| route.connected_omikron_ids.contains(omikron_id)) + { + return Err(format!("stale Iota index {omikron_id}:{iota_id}")); + } + } + } + Ok(()) + } + + pub fn check_index_consistency(&self) -> Result<(), String> { + Self::check_indices(&self.routes.read().unwrap()) + } + + pub fn set_preference(&self, user_id: i64, status: UserStatus) { + self.preferences.write().unwrap().insert(user_id, status); + } + + pub fn set_preferences(&self, preferences: HashMap) { + self.preferences.write().unwrap().extend(preferences); + } + + pub fn load_preference(&self, user_id: i64, raw: &str) -> bool { + let Some(status) = UserStatus::from_client_preference(raw) else { + self.set_preference(user_id, UserStatus::user_online); + return false; + }; + self.set_preference(user_id, status); + true + } + + pub fn preference(&self, user_id: i64) -> UserStatus { + self.preferences + .read() + .unwrap() + .get(&user_id) + .cloned() + .unwrap_or(UserStatus::user_online) + } + + pub fn has_iota_route(&self, iota_id: i64) -> bool { + self.routes + .read() + .unwrap() + .iotas + .get(&iota_id) + .is_some_and(|route| !route.connected_omikron_ids.is_empty()) + } + + pub fn has_active_session_for_iota(&self, user_id: i64, iota_id: i64) -> bool { + let routes = self.routes.read().unwrap(); + routes + .indices + .sessions_by_user + .get(&user_id) + .into_iter() + .flat_map(|session_ids| session_ids.iter()) + .any(|session_id| { + routes + .sessions + .get(&(user_id, *session_id)) + .is_some_and(|route| { + route.iota_id == iota_id + && routes.iotas.get(&route.iota_id).is_some_and(|iota| { + iota.connected_omikron_ids.contains(&route.omikron_id) + }) + }) + }) + } + + pub fn track_iota(&self, iota_id: i64, omikron_id: i64) { + self.connect_iota(iota_id, omikron_id); + } + + pub fn untrack_iota(&self, iota_id: i64, omikron_id: i64) -> bool { + self.disconnect_iota(iota_id, omikron_id) + } + + pub fn track_session(&self, user_id: i64, session_id: i64, omikron_id: i64, iota_id: i64) { + let mut routes = self.routes.write().unwrap(); + Self::insert_session_locked( + &mut routes, + (user_id, session_id), + ClientSessionRoute { + omikron_id, + iota_id, + }, + ); + Self::debug_assert_indices(&routes); + } + + pub fn remove_session(&self, user_id: i64, session_id: i64, omikron_id: i64) -> bool { + let mut routes = self.routes.write().unwrap(); + let removed = + Self::remove_session_locked(&mut routes, (user_id, session_id), Some(omikron_id)); + Self::debug_assert_indices(&routes); + removed + } + + pub fn owns_session(&self, user_id: i64, session_id: i64, omikron_id: i64) -> bool { + self.routes + .read() + .unwrap() + .sessions + .get(&(user_id, session_id)) + .is_some_and(|route| route.omikron_id == omikron_id) + } + + pub fn session_route(&self, user_id: i64, session_id: i64) -> Option { + self.routes + .read() + .unwrap() + .sessions + .get(&(user_id, session_id)) + .map(|route| route.clone()) + } + + pub fn resolve_public_state(&self, user_id: i64, iota_id: i64) -> UserStatus { + if !self.has_iota_route(iota_id) { + return UserStatus::iota_offline; + } + if !self.has_active_session_for_iota(user_id, iota_id) { + return UserStatus::user_offline; + } + self.preference(user_id).public_value() + } + + pub fn resolve_private_state(&self, user_id: i64) -> UserStatus { + self.preference(user_id) + } + + pub fn remove_omikron(&self, omikron_id: i64) -> RemovedOmikronState { + let mut routes = self.routes.write().unwrap(); + let iota_ids = routes + .indices + .iotas_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + for iota_id in &iota_ids { + Self::remove_iota_owner_locked(&mut routes, *iota_id, omikron_id); + } + + let subscriber_sessions = routes + .indices + .subscribers_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + + let session_keys = routes + .indices + .sessions_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + let session_user_ids = session_keys + .iter() + .map(|(user_id, _)| *user_id) + .collect::>(); + for key in &session_keys { + Self::remove_session_locked(&mut routes, *key, Some(omikron_id)); + } + for key in &subscriber_sessions { + Self::remove_subscription_locked(&mut routes, *key); + } + Self::debug_assert_indices(&routes); + + RemovedOmikronState { + iota_ids: sorted_vec(iota_ids), + session_user_ids: sorted_vec(session_user_ids), + subscriber_sessions: sorted_pairs(subscriber_sessions), + } + } + + pub fn remove_sessions_for_iota(&self, iota_id: i64, omikron_id: i64) { + let mut routes = self.routes.write().unwrap(); + let keys = routes + .indices + .sessions_by_omikron + .get(&omikron_id) + .into_iter() + .flat_map(|keys| keys.iter()) + .filter(|key| { + routes + .sessions + .get(key) + .is_some_and(|route| route.iota_id == iota_id) + }) + .copied() + .collect::>(); + for key in &keys { + Self::remove_session_locked(&mut routes, *key, Some(omikron_id)); + } + Self::debug_assert_indices(&routes); + } + + pub fn replace_subscription( + &self, + user_id: i64, + session_id: i64, + omikron_id: i64, + user_ids: Vec, + ) { + let mut routes = self.routes.write().unwrap(); + let key = (user_id, session_id); + Self::remove_subscription_locked(&mut routes, key); + for target_user_id in user_ids { + let subscriber = PresenceSubscriber { + user_id, + session_id, + omikron_id, + }; + routes + .subscriptions + .entry(target_user_id) + .or_default() + .insert(subscriber.clone()); + routes + .indices + .targets_by_subscriber + .entry(key) + .or_default() + .insert(target_user_id); + routes + .indices + .subscribers_by_omikron + .entry(omikron_id) + .or_default() + .insert(key); + } + Self::debug_assert_indices(&routes); + } + + pub fn remove_subscription(&self, user_id: i64, session_id: i64) { + let mut routes = self.routes.write().unwrap(); + Self::remove_subscription_locked(&mut routes, (user_id, session_id)); + Self::debug_assert_indices(&routes); + } + + pub fn subscribers(&self, target_user_id: i64) -> Vec { + let mut subscribers = self + .routes + .read() + .unwrap() + .subscriptions + .get(&target_user_id) + .cloned() + .unwrap_or_default() + .into_iter() + .collect::>(); + subscribers.sort_by_key(|subscriber| { + ( + subscriber.omikron_id, + subscriber.user_id, + subscriber.session_id, + ) + }); + subscribers + } + + pub fn sessions_owned_by(&self, omikron_id: i64) -> Vec<(i64, i64, ClientSessionRoute)> { + let routes = self.routes.read().unwrap(); + let mut sessions = routes + .indices + .sessions_by_omikron + .get(&omikron_id) + .into_iter() + .flat_map(|keys| keys.iter()) + .filter_map(|key| { + routes + .sessions + .get(key) + .map(|route| (key.0, key.1, route.clone())) + }) + .collect::>(); + sessions.sort_by_key(|(user_id, session_id, _)| (*user_id, *session_id)); + sessions + } + + pub fn replace_routes_owned_by( + &self, + omikron_id: i64, + iota_ids: &[i64], + sessions: &[(i64, i64, i64)], + ) { + self.replace_omikron_snapshot(omikron_id, iota_ids, sessions); + } + + pub fn replace_omikron_snapshot( + &self, + omikron_id: i64, + iota_ids: &[i64], + sessions: &[(i64, i64, i64)], + ) { + let mut routes = self.routes.write().unwrap(); + let old_sessions = routes + .indices + .sessions_by_omikron + .get(&omikron_id) + .cloned() + .unwrap_or_default(); + let old_subscriber_sessions = routes + .indices + .subscribers_by_omikron + .remove(&omikron_id) + .unwrap_or_default(); + for key in &old_subscriber_sessions { + Self::remove_subscription_locked(&mut routes, *key); + } + for key in &old_sessions { + Self::remove_session_locked(&mut routes, *key, Some(omikron_id)); + } + let old_iota_ids = routes + .indices + .iotas_by_omikron + .get(&omikron_id) + .cloned() + .unwrap_or_default(); + for iota_id in old_iota_ids { + Self::remove_iota_owner_locked(&mut routes, iota_id, omikron_id); + } + for iota_id in iota_ids { + let route = routes.iotas.entry(*iota_id).or_default(); + route.connected_omikron_ids.insert(omikron_id); + route.primary_omikron_id = + select_primary_route(route.primary_omikron_id, &route.connected_omikron_ids); + routes + .indices + .iotas_by_omikron + .entry(omikron_id) + .or_default() + .insert(*iota_id); + } + for (user_id, session_id, iota_id) in sessions { + Self::insert_session_locked( + &mut routes, + (*user_id, *session_id), + ClientSessionRoute { + omikron_id, + iota_id: *iota_id, + }, + ); + } + Self::debug_assert_indices(&routes); + } + + pub fn iota_ids_owned_by(&self, omikron_id: i64) -> Vec { + let routes = self.routes.read().unwrap(); + sorted_vec( + routes + .indices + .iotas_by_omikron + .get(&omikron_id) + .cloned() + .unwrap_or_default(), + ) + } + + pub fn track_iota_connection(&self, iota_id: i64, omikron_id: i64, primary: bool) { + self.connect_iota_with_priority(iota_id, omikron_id, primary); + } + + pub fn untrack_iota_connection(&self, iota_id: i64, omikron_id: i64) -> bool { + let connections_empty = self.disconnect_iota(iota_id, omikron_id); + self.remove_sessions_for_iota(iota_id, omikron_id); + connections_empty + } + + pub fn primary_iota_route(&self, iota_id: i64) -> Option { + self.routes + .read() + .unwrap() + .iotas + .get(&iota_id) + .and_then(|route| route.primary_omikron_id) + } + + pub fn iota_connections(&self, iota_id: i64) -> Option> { + self.all_iota_routes().get(&iota_id).map(|route| { + let mut ids = route + .connected_omikron_ids + .iter() + .copied() + .collect::>(); + ids.sort_unstable(); + ids + }) + } + + pub fn routes_for_user(&self, user_id: i64) -> Vec { + let routes = self.routes.read().unwrap(); + let mut user_routes = routes + .indices + .sessions_by_user + .get(&user_id) + .into_iter() + .flat_map(|session_ids| session_ids.iter()) + .filter_map(|session_id| { + routes + .sessions + .get(&(user_id, *session_id)) + .map(|route| (*session_id, route.clone())) + }) + .collect::>(); + user_routes.sort_by_key(|(session_id, route)| (*session_id, route.omikron_id)); + user_routes.into_iter().map(|(_, route)| route).collect() + } + + pub fn sessions_for_user(&self, user_id: i64) -> Vec<(i64, ClientSessionRoute)> { + let routes = self.routes.read().unwrap(); + let mut sessions = routes + .indices + .sessions_by_user + .get(&user_id) + .into_iter() + .flat_map(|session_ids| session_ids.iter()) + .filter_map(|session_id| { + routes + .sessions + .get(&(user_id, *session_id)) + .map(|route| (*session_id, route.clone())) + }) + .collect::>(); + sessions.sort_by_key(|(session_id, route)| (*session_id, route.omikron_id)); + sessions + } + + pub fn route_for_session(&self, user_id: i64, session_id: i64) -> Option { + self.session_route(user_id, session_id) + } + + pub fn user_route(&self, user_id: i64) -> Option { + self.routes_for_user(user_id).into_iter().next() + } + + pub fn connect_iota(&self, iota_id: i64, omikron_id: i64) { + self.connect_iota_with_priority(iota_id, omikron_id, false); + } + + fn connect_iota_with_priority(&self, iota_id: i64, omikron_id: i64, primary: bool) { + let mut routes = self.routes.write().unwrap(); + let route = routes.iotas.entry(iota_id).or_default(); + route.connected_omikron_ids.insert(omikron_id); + if primary || route.primary_omikron_id.is_none() { + route.primary_omikron_id = Some(omikron_id); + } + routes + .indices + .iotas_by_omikron + .entry(omikron_id) + .or_default() + .insert(iota_id); + Self::debug_assert_indices(&routes); + } + + pub fn disconnect_iota(&self, iota_id: i64, omikron_id: i64) -> bool { + let mut routes = self.routes.write().unwrap(); + let empty = Self::remove_iota_owner_locked(&mut routes, iota_id, omikron_id); + Self::debug_assert_indices(&routes); + empty + } + + pub fn all_iota_routes(&self) -> HashMap { + self.routes.read().unwrap().iotas.clone() + } + + pub fn connection_routes(&self) -> HashMap>> { + let mut result: std::collections::HashMap>> = + std::collections::HashMap::new(); + + let iotas = self.all_iota_routes(); + for (iota_id, route) in &iotas { + for omikron_id in &route.connected_omikron_ids { + result + .entry(*omikron_id) + .or_default() + .entry(*iota_id) + .or_default(); + } + } + result + } + + pub fn remove_omikron_and_offline_users(&self, omikron_id: i64) -> RemovedOmikronState { + self.remove_omikron(omikron_id) + } +} + +#[cfg(test)] +mod tests { + use super::{ClientSessionRoute, PresenceTracker}; + use crate::sql::connection_status::UserStatus; + + #[test] + fn effective_state_is_derived_from_routes_and_preference() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_online); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::iota_offline + ); + + tracker.track_iota(11, 42); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::user_offline + ); + + tracker.track_session(7, 3, 42, 11); + assert_eq!(tracker.resolve_public_state(7, 11), UserStatus::user_online); + tracker.set_preference(7, UserStatus::user_invisible); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::user_offline + ); + } + + #[test] + fn session_must_match_assigned_iota_and_live_route() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_session(7, 3, 42, 12); + assert!(!tracker.has_active_session_for_iota(7, 11)); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::user_offline + ); + + tracker.track_session(7, 3, 43, 11); + assert!(!tracker.has_active_session_for_iota(7, 11)); + + tracker.track_session(7, 3, 42, 11); + assert!(tracker.has_active_session_for_iota(7, 11)); + tracker.disconnect_iota(11, 42); + assert!(!tracker.has_active_session_for_iota(7, 11)); + assert_eq!( + tracker.resolve_public_state(7, 11), + UserStatus::iota_offline + ); + } + + #[test] + fn resolver_covers_all_public_preference_states() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_session(7, 3, 42, 11); + + for (preference, expected) in [ + (UserStatus::user_online, UserStatus::user_online), + (UserStatus::user_idle, UserStatus::user_idle), + (UserStatus::user_dnd, UserStatus::user_dnd), + (UserStatus::user_wc, UserStatus::user_wc), + (UserStatus::user_invisible, UserStatus::user_offline), + ] { + tracker.set_preference(7, preference); + assert_eq!(tracker.resolve_public_state(7, 11), expected); + } + } + + #[test] + fn session_routes_replace_by_session_without_replacing_preferences() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_dnd); + tracker.track_iota(11, 42); + tracker.track_iota(12, 43); + tracker.track_session(7, 3, 42, 11); + tracker.track_session(7, 4, 43, 12); + + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + assert_eq!( + tracker.session_route(7, 3), + Some(ClientSessionRoute { + omikron_id: 42, + iota_id: 11, + }) + ); + + tracker.remove_session(7, 3, 42); + assert!(!tracker.owns_session(7, 3, 42)); + assert!(tracker.owns_session(7, 4, 43)); + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + } + + #[test] + fn subscriptions_are_authoritative_and_cleanup_is_scoped() { + let tracker = PresenceTracker::default(); + tracker.replace_subscription(7, 3, 42, vec![20, 21, 20]); + tracker.replace_subscription(7, 3, 42, vec![21, 22]); + tracker.replace_subscription(8, 4, 43, vec![21]); + + assert_eq!(tracker.subscribers(20).len(), 0); + assert_eq!(tracker.subscribers(21).len(), 2); + assert_eq!(tracker.subscribers(22).len(), 1); + + tracker.remove_omikron(42); + assert!(tracker.subscribers(22).is_empty()); + assert_eq!(tracker.subscribers(21).len(), 1); + } + + #[test] + fn losing_one_iota_route_keeps_iota_online() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_iota(11, 43); + assert!(tracker.has_iota_route(11)); + + assert!(!tracker.untrack_iota(11, 42)); + assert!(tracker.has_iota_route(11)); + assert!(tracker.untrack_iota(11, 43)); + assert!(!tracker.has_iota_route(11)); + } + + #[test] + fn iota_connection_routes_track_primary_and_replacement() { + let tracker = PresenceTracker::default(); + tracker.track_iota_connection(11, 42, true); + tracker.track_iota_connection(11, 43, false); + + assert_eq!(tracker.iota_connections(11), Some(vec![42, 43])); + assert_eq!(tracker.primary_iota_route(11), Some(42)); + + tracker.untrack_iota_connection(11, 42); + + assert_eq!(tracker.iota_connections(11), Some(vec![43])); + assert_eq!(tracker.primary_iota_route(11), Some(43)); + } + + #[test] + fn snapshot_preserves_primary_and_uses_deterministic_fallback() { + let tracker = PresenceTracker::default(); + tracker.connect_iota(11, 20); + tracker.connect_iota(11, 30); + assert_eq!(tracker.primary_iota_route(11), Some(20)); + + tracker.replace_omikron_snapshot(30, &[11], &[]); + assert_eq!(tracker.primary_iota_route(11), Some(20)); + + tracker.replace_omikron_snapshot(20, &[11], &[]); + assert_eq!(tracker.primary_iota_route(11), Some(30)); + tracker.replace_omikron_snapshot(30, &[11], &[]); + assert_eq!(tracker.primary_iota_route(11), Some(20)); + } + + #[test] + fn user_routes_are_sorted_by_session_then_omikron() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 20, 30, 11); + tracker.track_session(7, 10, 40, 11); + assert_eq!( + tracker + .routes_for_user(7) + .iter() + .map(|route| route.omikron_id) + .collect::>(), + vec![40, 30] + ); + + tracker.remove_session(7, 10, 40); + assert_eq!(tracker.routes_for_user(7).len(), 1); + } + + #[test] + fn stale_disconnect_cannot_remove_current_session_subscription() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 3, 42, 11); + tracker.replace_subscription(7, 3, 42, vec![20]); + + assert!(!tracker.remove_session(7, 3, 99)); + assert!(tracker.owns_session(7, 3, 42)); + assert_eq!(tracker.subscribers(20).len(), 1); + + assert!(tracker.remove_session(7, 3, 42)); + assert!(tracker.subscribers(20).is_empty()); + } + + #[test] + fn private_state_is_not_affected_by_connectivity() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_invisible); + assert_eq!(tracker.resolve_private_state(7), UserStatus::user_invisible); + } + + #[test] + fn invalid_persisted_preference_falls_back_to_online() { + let tracker = PresenceTracker::default(); + assert!(!tracker.load_preference(7, "user_offline")); + assert_eq!(tracker.preference(7), UserStatus::user_online); + assert!(!tracker.load_preference(7, "iota_offline")); + assert_eq!(tracker.preference(7), UserStatus::user_online); + assert!(tracker.load_preference(7, "user_invisible")); + assert_eq!(tracker.preference(7), UserStatus::user_invisible); + assert!(tracker.load_preference(7, "user_dnd")); + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + } + + #[test] + fn active_session_can_use_one_of_several_live_iota_routes() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_iota(11, 43); + tracker.track_session(7, 3, 43, 11); + + assert!(tracker.has_active_session_for_iota(7, 11)); + tracker.disconnect_iota(11, 43); + assert!(!tracker.has_active_session_for_iota(7, 11)); + } + + #[test] + fn route_snapshot_replaces_only_one_omikron() { + let tracker = PresenceTracker::default(); + tracker.track_iota(11, 42); + tracker.track_iota(12, 43); + tracker.track_session(7, 3, 42, 11); + tracker.track_session(8, 4, 43, 12); + tracker.replace_subscription(7, 3, 42, vec![20]); + + tracker.replace_routes_owned_by(42, &[12], &[(9, 5, 12)]); + + assert!(!tracker.owns_session(7, 3, 42)); + assert!(tracker.owns_session(8, 4, 43)); + assert!(tracker.owns_session(9, 5, 42)); + assert!(tracker.subscribers(20).is_empty()); + assert!(tracker.has_iota_route(12)); + } + + #[test] + fn omikron_cleanup_preserves_other_routes_and_preferences() { + let tracker = PresenceTracker::default(); + tracker.set_preference(7, UserStatus::user_dnd); + tracker.track_iota(11, 42); + tracker.track_iota(11, 43); + tracker.track_session(7, 3, 42, 11); + tracker.track_session(7, 4, 43, 11); + tracker.replace_subscription(7, 3, 42, vec![20]); + tracker.replace_subscription(7, 4, 43, vec![21]); + + tracker.remove_omikron(42); + + assert!(tracker.has_iota_route(11)); + assert!(!tracker.owns_session(7, 3, 42)); + assert!(tracker.owns_session(7, 4, 43)); + assert!(tracker.subscribers(20).is_empty()); + assert_eq!(tracker.subscribers(21).len(), 1); + assert_eq!(tracker.preference(7), UserStatus::user_dnd); + } + + #[test] + fn secondary_indices_follow_session_and_subscription_mutations() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 3, 42, 11); + tracker.replace_subscription(7, 3, 42, vec![20, 21, 20]); + assert_eq!( + tracker + .routes + .read() + .unwrap() + .indices + .sessions_by_user + .get(&7) + .cloned(), + Some([3].into_iter().collect()) + ); + assert_eq!( + tracker + .routes + .read() + .unwrap() + .indices + .targets_by_subscriber + .get(&(7, 3)) + .cloned(), + Some([20, 21].into_iter().collect()) + ); + assert!(tracker.check_index_consistency().is_ok()); + + tracker.replace_subscription(7, 3, 42, vec![22]); + assert!(tracker.subscribers(20).is_empty()); + assert!(tracker.subscribers(21).is_empty()); + assert_eq!(tracker.subscribers(22).len(), 1); + tracker.remove_session(7, 3, 42); + assert!(tracker.check_index_consistency().is_ok()); + assert!(tracker + .routes + .read() + .unwrap() + .indices + .sessions_by_user + .is_empty()); + assert!(tracker + .routes + .read() + .unwrap() + .indices + .targets_by_subscriber + .is_empty()); + } + + #[test] + fn omikron_removal_returns_all_affected_identifiers() { + let tracker = PresenceTracker::default(); + tracker.connect_iota(11, 42); + tracker.track_session(7, 3, 42, 11); + tracker.replace_subscription(7, 3, 42, vec![20]); + + let removed = tracker.remove_omikron(42); + + assert_eq!(removed.iota_ids, vec![11]); + assert_eq!(removed.session_user_ids, vec![7]); + assert_eq!(removed.subscriber_sessions, vec![(7, 3)]); + assert!(tracker.check_index_consistency().is_ok()); + } + + #[test] + fn consistency_check_reports_corrupted_reverse_index() { + let tracker = PresenceTracker::default(); + tracker.track_session(7, 3, 42, 11); + tracker + .routes + .write() + .unwrap() + .indices + .sessions_by_user + .get_mut(&7) + .unwrap() + .clear(); + + assert!(tracker.check_index_consistency().is_err()); } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..dc91021 --- /dev/null +++ b/src/state.rs @@ -0,0 +1,72 @@ +use crate::sql::user_online_tracker::PresenceTracker; +use std::sync::Arc; + +pub struct OmegaState { + pub presence: Arc, +} + +impl Default for OmegaState { + fn default() -> Self { + Self { + presence: Arc::new(PresenceTracker::default()), + } + } +} + +impl OmegaState { + pub fn new() -> Arc { + Arc::new(Self::default()) + } +} + +#[cfg(test)] +mod tests { + use super::OmegaState; + use crate::sql::connection_status::UserStatus; + + #[test] + fn state_instances_have_independent_presence_trackers() { + let first = OmegaState::new(); + let second = OmegaState::new(); + + first.presence.track_iota_connection(11, 42, true); + + assert!(first.presence.has_iota_route(11)); + assert!(!second.presence.has_iota_route(11)); + assert_eq!(first.presence.primary_iota_route(11), Some(42)); + assert_eq!(second.presence.primary_iota_route(11), None); + } + + #[test] + fn two_session_private_and_public_presence_flow_is_authoritative() { + let state = OmegaState::new(); + state.presence.set_preference(7, UserStatus::user_online); + state.presence.set_preference(8, UserStatus::user_online); + state.presence.track_iota_connection(11, 42, true); + state.presence.track_session(7, 100, 42, 11); + state.presence.track_session(7, 101, 42, 11); + state.presence.replace_subscription(7, 100, 42, vec![8]); + + assert_eq!( + state.presence.resolve_public_state(8, 11), + UserStatus::user_offline + ); + state.presence.set_preference(8, UserStatus::user_invisible); + assert_eq!( + state.presence.resolve_public_state(8, 11), + UserStatus::user_offline + ); + assert_eq!( + state.presence.resolve_private_state(8), + UserStatus::user_invisible + ); + + state.presence.remove_session(7, 100, 42); + assert!(state.presence.owns_session(7, 101, 42)); + state.presence.remove_session(7, 101, 42); + assert_eq!( + state.presence.resolve_public_state(7, 11), + UserStatus::user_offline + ); + } +} diff --git a/src/transport/capabilities.rs b/src/transport/capabilities.rs new file mode 100644 index 0000000..0a3c685 --- /dev/null +++ b/src/transport/capabilities.rs @@ -0,0 +1,147 @@ +use std::collections::BTreeSet; + +const OMIKRON_PREFIX: &str = "omikron;caps="; +const OMEGA_PREFIX: &str = "omega;caps="; +const SET_USER_STATE: &str = "set_user_state_v1"; +const STATE_SUBSCRIBE: &str = "state_subscribe_v1"; +const SESSION_SNAPSHOT: &str = "session_snapshot_v1"; +const CLIENT_STATE_PUSH: &str = "client_state_push_v1"; + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct PeerCapabilities { + pub set_user_state_v1: bool, + pub state_subscribe_v1: bool, + pub session_snapshot_v1: bool, + pub client_state_push_v1: bool, +} + +impl PeerCapabilities { + /// A missing descriptor is the legacy protocol: tuple route snapshots, + /// GetStates-only subscription refreshes, and ClientChanged pushes. + pub fn from_identification_description(description: Option<&str>) -> Result { + parse_capabilities(description, OMIKRON_PREFIX) + } +} + +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct OmegaCapabilities { + pub set_user_state_v1: bool, + pub state_subscribe_v1: bool, + pub session_snapshot_v1: bool, + pub client_state_push_v1: bool, +} + +impl OmegaCapabilities { + pub fn current() -> Self { + Self { + set_user_state_v1: true, + state_subscribe_v1: true, + session_snapshot_v1: true, + client_state_push_v1: true, + } + } + + pub fn identification_description(&self) -> String { + let mut names = Vec::new(); + if self.set_user_state_v1 { + names.push(SET_USER_STATE); + } + if self.state_subscribe_v1 { + names.push(STATE_SUBSCRIBE); + } + if self.session_snapshot_v1 { + names.push(SESSION_SNAPSHOT); + } + if self.client_state_push_v1 { + names.push(CLIENT_STATE_PUSH); + } + format!("{OMEGA_PREFIX}{}", names.join(",")) + } +} + +fn parse_capabilities(description: Option<&str>, prefix: &str) -> Result { + let Some(description) = description else { + return Ok(PeerCapabilities::default()); + }; + if description == "omikron" { + return Ok(PeerCapabilities::default()); + } + let Some(capabilities) = description.strip_prefix(prefix) else { + return Err(()); + }; + let mut seen = BTreeSet::new(); + for capability in capabilities.split(',') { + if capability.is_empty() || !seen.insert(capability) { + return Err(()); + } + } + if seen.iter().any(|capability| { + !matches!( + *capability, + SET_USER_STATE | STATE_SUBSCRIBE | SESSION_SNAPSHOT | CLIENT_STATE_PUSH + ) + }) { + return Err(()); + } + Ok(PeerCapabilities { + set_user_state_v1: seen.contains(SET_USER_STATE), + state_subscribe_v1: seen.contains(STATE_SUBSCRIBE), + session_snapshot_v1: seen.contains(SESSION_SNAPSHOT), + client_state_push_v1: seen.contains(CLIENT_STATE_PUSH), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn advertised_capabilities_are_parsed() { + let capabilities = PeerCapabilities::from_identification_description(Some( + "omikron;caps=set_user_state_v1,state_subscribe_v1,session_snapshot_v1,client_state_push_v1", + )).unwrap(); + assert!(capabilities.set_user_state_v1); + assert!(capabilities.state_subscribe_v1); + assert!(capabilities.session_snapshot_v1); + assert!(capabilities.client_state_push_v1); + } + + #[test] + fn invalid_capability_values_fail_identification() { + assert!( + PeerCapabilities::from_identification_description(Some("omikron;caps=unsupported")) + .is_err() + ); + } + + #[test] + fn legacy_peer_has_no_version_specific_features() { + let capabilities = PeerCapabilities::from_identification_description(None).unwrap(); + assert!(!capabilities.set_user_state_v1); + assert!(!capabilities.state_subscribe_v1); + assert!(!capabilities.session_snapshot_v1); + assert!(!capabilities.client_state_push_v1); + assert_eq!( + PeerCapabilities::from_identification_description(Some("omikron")), + Ok(PeerCapabilities::default()) + ); + } + + #[test] + fn reconnecting_with_the_same_identification_is_stable() { + let description = Some( + "omikron;caps=set_user_state_v1,state_subscribe_v1,session_snapshot_v1,client_state_push_v1", + ); + assert_eq!( + PeerCapabilities::from_identification_description(description), + PeerCapabilities::from_identification_description(description) + ); + } + + #[test] + fn omega_capability_description_is_distinct_from_omikron_capabilities() { + let description = OmegaCapabilities::current().identification_description(); + assert!(description.starts_with(OMEGA_PREFIX)); + assert!(PeerCapabilities::from_identification_description(Some(&description)).is_err()); + } +} diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index adaa1a2..0c31904 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -1,69 +1,207 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; use crate::{ - db::user_repo, - log_in, - models::IotaId, - sql::{connection_status::UserStatus, user_online_tracker}, + db::user_repo, log_in, models::IotaId, sql::connection_status::UserStatus, state::OmegaState, }; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; -use std::sync::Arc; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + sync::Arc, +}; -pub async fn user_connected( - _connection: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "User connected"); - if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - let status = value - .get_data(DataType::UserState) - .as_str() - .and_then(UserStatus::from_str) - .unwrap_or(UserStatus::user_online); - if let Ok(user_id) = i64::try_from(user_id) { - if let Some(session_id) = value - .get_data(DataType::SessionId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) - { - user_online_tracker::track_user_session_status( - user_id, session_id, status, omikron_id, - ); - } else { - user_online_tracker::track_user_status(user_id, status, omikron_id); - } +fn parse_subscription(value: &CommunicationValue) -> Result<(i64, i64, Vec), &'static str> { + let user_id = i64::try_from(value.get_sender()) + .ok() + .filter(|id| *id > 0) + .ok_or("user_id")?; + let session_id = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + .ok_or("session_id")?; + let DataValue::Array(values) = value.get_data(DataType::UserIds) else { + return Err("user_ids"); + }; + + let mut user_ids = Vec::with_capacity(values.len()); + for value in values { + let DataValue::SignedNumber(user_id) = value else { + return Err("user_ids"); + }; + let Ok(user_id) = i64::try_from(*user_id) else { + return Err("user_ids"); + }; + if user_id <= 0 { + return Err("user_ids"); + } + if !user_ids.contains(&user_id) { + user_ids.push(user_id); } } - Ok(()) + Ok((user_id, session_id, user_ids)) } -pub async fn user_disconnected( - _: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); - if let Some(user_id) = value.get_data(DataType::UserId).as_number() { - if let Some(session_id) = value - .get_data(DataType::SessionId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) +fn apply_preferences(state: &OmegaState, preferences: HashMap) { + state.presence.set_preferences(preferences); +} + +fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMap { + users + .iter() + .map(|user| { + ( + user.id.0, + state + .presence + .resolve_public_state(user.id.0, user.iota_id.0), + ) + }) + .collect() +} + +fn changed_states( + state: &OmegaState, + before: &HashMap, + users: &[crate::models::User], +) -> Vec<(i64, UserStatus)> { + let mut changes = users + .iter() + .filter_map(|user| { + let after = state + .presence + .resolve_public_state(user.id.0, user.iota_id.0); + (before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after)) + }) + .collect::>(); + changes.sort_by_key(|(user_id, _)| *user_id); + changes.dedup_by_key(|(user_id, _)| *user_id); + changes +} + +fn state_notification( + subscriber: &crate::sql::user_online_tracker::PresenceSubscriber, + user_id: i64, + user_state: &UserStatus, +) -> CommunicationValue { + CommunicationValue::new(CommunicationType::ClientChanged) + .with_receiver(subscriber.user_id as u64) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(subscriber.session_id.into()), + ) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) +} + +fn private_state_notification( + user_id: i64, + session_id: i64, + user_state: &UserStatus, +) -> CommunicationValue { + CommunicationValue::new(CommunicationType::ClientChanged) + .with_receiver(user_id as u64) + .add_typed_default( + DataType::SessionId, + DataValue::SignedNumber(session_id.into()), + ) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) +} + +async fn publish_state_changes(state: &OmegaState, changes: &[(i64, UserStatus)]) { + let mut grouped = BTreeMap::>::new(); + for (user_id, user_state) in changes { + for subscriber in state.presence.subscribers(*user_id) { + grouped + .entry(subscriber.omikron_id) + .or_default() + .push(state_notification(&subscriber, *user_id, user_state)); + } + } + for (omikron_id, notifications) in grouped { + if let Err(error) = + crate::transport::omikron_manager::send_state_batch(omikron_id, notifications).await { - user_online_tracker::untrack_user_session_status( - user_id as i64, - session_id, + log_in!( + crate::util::logger::PrintType::General, + "Failed to deliver presence state batch to Omikron {}: {}", omikron_id, + error ); - } else { - user_online_tracker::untrack_user_status(user_id as i64, omikron_id); } } - Ok(()) } -pub async fn client_changed( +async fn publish_changed_states( + state: &OmegaState, + before: &HashMap, + users: &[crate::models::User], +) { + publish_state_changes(state, &changed_states(state, before, users)).await; +} + +async fn publish_private_state(state: &OmegaState, user_id: i64, user_state: &UserStatus) { + let mut grouped = BTreeMap::>::new(); + for (session_id, route) in state.presence.sessions_for_user(user_id) { + grouped + .entry(route.omikron_id) + .or_default() + .push(private_state_notification(user_id, session_id, user_state)); + } + for (omikron_id, notifications) in grouped { + if let Err(error) = + crate::transport::omikron_manager::send_state_batch(omikron_id, notifications).await + { + log_in!( + crate::util::logger::PrintType::General, + "Failed to deliver private presence state batch to Omikron {}: {}", + omikron_id, + error + ); + } + } +} + +pub async fn state_subscribe( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + let (user_id, session_id, user_ids) = match parse_subscription(&value) { + Ok(subscription) => subscription, + Err("user_id") => { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) + .await; + } + Err(detail) => { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + detail, + ) + .await; + } + }; + if !state.presence.owns_session(user_id, session_id, omikron_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + state + .presence + .replace_subscription(user_id, session_id, omikron_id, user_ids); + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await +} + +/// Legacy state-change contract used by Omegas that predate SetUserState. +/// The payload is ClientChanged with UserId and UserState only. +pub async fn client_changed_legacy( + state: Arc, _: Arc, value: CommunicationValue, _: i64, @@ -72,29 +210,266 @@ pub async fn client_changed( .get_data(DataType::UserId) .as_number() .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { return Ok(()); }; let Some(status) = value .get_data(DataType::UserState) .as_str() - .and_then(UserStatus::from_str) + .and_then(UserStatus::from_client_preference) else { return Ok(()); }; - // Connectivity is derived from routes. Clients may choose only public - // presence preferences, never server/offline states. - if matches!( - status, - UserStatus::user_offline | UserStatus::iota_offline | UserStatus::iota_online - ) { - return Ok(()); - } - user_online_tracker::update_user_session_status(user_id, status); + state.presence.set_preference(user_id, status); Ok(()) } +pub async fn user_connected( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User connected"); + let Some(user_id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let user = match user_repo::get_by_user_id(user_id.into()).await { + Ok(user) => user, + Err(crate::error::OmegaError::NotFound) => { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + } + Err(error) => return Err(error.into()), + }; + let preferences = match user_repo::get_presence_preferences(&[user_id]).await { + Ok(preferences) => preferences, + Err(error) => return Err(error.into()), + }; + if user.iota_id.0 != iota_id || !state.presence.has_iota_route(iota_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + apply_preferences(&state, preferences); + let users = [user]; + let before = states_for_users(&state, &users); + state + .presence + .track_session(user_id, session_id, omikron_id, iota_id); + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await +} + +pub async fn user_disconnected( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + log_in!(crate::util::logger::PrintType::Omega, "User disconnected"); + let Some(user_id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + }; + if let Ok(user) = user_repo::get_by_user_id(user_id.into()).await { + let preferences = user_repo::get_presence_preferences(&[user_id]).await?; + apply_preferences(&state, preferences); + let users = [user]; + let before = states_for_users(&state, &users); + state + .presence + .remove_session(user_id, session_id, omikron_id); + publish_changed_states(&state, &before, &users).await; + } else { + state + .presence + .remove_session(user_id, session_id, omikron_id); + } + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await +} + +pub async fn set_user_state( + state: Arc, + connection: Arc, + value: CommunicationValue, + omikron_id: i64, +) -> OmikronResult<()> { + let Some(user_id) = i64::try_from(value.get_sender()).ok().filter(|id| *id > 0) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) + .await; + }; + if let Some(requested_user) = value.get_data_opt(DataType::UserId) { + let Some(requested_user_id) = requested_user + .as_number() + .and_then(|id| i64::try_from(id).ok()) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "user_id", + ) + .await; + }; + if requested_user_id != user_id { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "user_id", + ) + .await; + } + } + let Some(iota_id) = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "iota_id", + ) + .await; + }; + let Some(requested_state) = value + .get_data(DataType::UserState) + .as_str() + .and_then(UserStatus::from_client_preference) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "user_state", + ) + .await; + }; + if !state.presence.has_iota_route(iota_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + let Some(session_id) = value + .get_data(DataType::SessionId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response_with_detail( + value.get_id(), + CommunicationType::ErrorInvalidData, + "session_id", + ) + .await; + }; + let Some(route) = state.presence.session_route(user_id, session_id) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + }; + if route.omikron_id != omikron_id || route.iota_id != iota_id { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; + } + if !state.presence.has_active_session_for_iota(user_id, iota_id) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .await; + } + let previous_preference = state.presence.preference(user_id); + let previous_state = state.presence.resolve_public_state(user_id, iota_id); + if let Err(error) = + user_repo::change_presence_preference(user_id.into(), requested_state.to_string()).await + { + log_in!( + crate::util::logger::PrintType::General, + "Failed to persist presence preference: {}", + error + ); + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .await; + } + state + .presence + .set_preference(user_id, requested_state.clone()); + let new_state = state.presence.resolve_public_state(user_id, iota_id); + if requested_state != previous_preference { + publish_private_state(&state, user_id, &requested_state).await; + } + if requested_state != previous_preference && new_state != previous_state { + publish_state_changes(&state, &[(user_id, new_state)]).await; + } + connection + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.get_id()) + .add_typed_default( + DataType::UserState, + DataValue::Str(requested_state.to_string()), + ), + ) + .await +} + pub async fn iota_connected( + state: Arc, connection: Arc, value: CommunicationValue, omikron_id: i64, @@ -105,36 +480,32 @@ pub async fn iota_connected( .as_number() .map(|id| id as i64) else { - return Ok(()); + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; }; - user_online_tracker::track_iota_connection(iota_id, omikron_id, true); - let mut user_ids = Vec::new(); - match user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - Ok(users) => { - for user in users { - user_ids.push(DataValue::SignedNumber(user.id.0.into())); - user_online_tracker::track_user_status( - user.id.0, - UserStatus::user_offline, - omikron_id, - ); - } - } - Err(_) => log_in!( - crate::util::logger::PrintType::General, - "SQL error loading users for IOTA" - ), - } + let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; + let ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences(&state, user_repo::get_presence_preferences(&ids).await?); + let before = states_for_users(&state, &users); + state.presence.connect_iota(iota_id, omikron_id); + let user_ids = users + .iter() + .map(|user| DataValue::SignedNumber(user.id.0.into())) + .collect(); let response = CommunicationValue::new(CommunicationType::IotaUserData) - .with_id(value.get_id()) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); - let _ = connection.send(&response).await; - Ok(()) + connection.clone().send(&response).await?; + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await } pub async fn iota_disconnected( - _: Arc, + state: Arc, + connection: Arc, value: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { @@ -144,40 +515,317 @@ pub async fn iota_disconnected( .as_number() .map(|id| id as i64) else { - return Ok(()); + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .await; }; - if user_online_tracker::untrack_iota_connection(iota_id, omikron_id) { - if let Ok(users) = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await { - user_online_tracker::untrack_many_users( - &users.iter().map(|user| user.id.0).collect::>(), - ); - } - } - Ok(()) + let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; + let ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences(&state, user_repo::get_presence_preferences(&ids).await?); + let before = states_for_users(&state, &users); + state.presence.untrack_iota_connection(iota_id, omikron_id); + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await } pub async fn sync_status( - _: Arc, + state: Arc, + connection: Arc, value: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { - if let DataValue::Array(ids) = value.get_data(DataType::UserIds) { - for id in ids { - if let DataValue::SignedNumber(id) = id { - user_online_tracker::track_user_status( - *id as i64, - UserStatus::user_offline, - omikron_id, - ); - } + let request_id = value.get_id(); + let DataValue::Array(iota_values) = value.get_data(DataType::IotaIds) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let DataValue::Array(session_values) = value.get_data(DataType::UserStates) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + + let mut iota_ids = Vec::with_capacity(iota_values.len()); + for item in iota_values { + let DataValue::SignedNumber(id) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let Ok(id) = i64::try_from(*id) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + if id <= 0 { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + if !iota_ids.contains(&id) { + iota_ids.push(id); } } - if let DataValue::Array(ids) = value.get_data(DataType::IotaIds) { - for id in ids { - if let DataValue::SignedNumber(id) = id { - user_online_tracker::track_iota_connection(*id as i64, omikron_id, true); + + if !connection.peer_capabilities().session_snapshot_v1 { + let DataValue::Array(user_values) = value.get_data(DataType::UserIds) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let mut user_ids = Vec::with_capacity(user_values.len()); + for item in user_values { + let DataValue::SignedNumber(user_id) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let Ok(user_id) = i64::try_from(*user_id) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + if user_id <= 0 { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + if !user_ids.contains(&user_id) { + user_ids.push(user_id); } } + + let previous_iota_ids = state.presence.iota_ids_owned_by(omikron_id); + let affected_iota_ids = previous_iota_ids + .iter() + .chain(iota_ids.iter()) + .copied() + .collect::>(); + let users = user_repo::get_users_by_ids(&user_ids).await?; + let returned_user_ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences( + &state, + user_repo::get_presence_preferences(&returned_user_ids).await?, + ); + let before = states_for_users(&state, &users); + state + .presence + .replace_omikron_snapshot(omikron_id, &iota_ids, &[]); + let affected_users = user_repo::get_users_by_ids_and_iota_ids( + &returned_user_ids, + &affected_iota_ids.iter().copied().collect::>(), + ) + .await?; + publish_changed_states(&state, &before, &affected_users).await; + return connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(request_id)) + .await; + } + + let tm = mtp::type_map::TypeMap::latest(); + let mut sessions = Vec::with_capacity(session_values.len()); + for item in session_values { + let (user_id, session_id, iota_id) = if connection.peer_capabilities().session_snapshot_v1 { + let DataValue::Container(entries) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let find = |kind| { + entries.iter().find_map(|(key, value)| { + (Some(*key) == DataType::try_to_id(kind, &tm)).then_some(value) + }) + }; + let ( + Some(DataValue::SignedNumber(user_id)), + Some(DataValue::SignedNumber(session_id)), + Some(DataValue::SignedNumber(iota_id)), + ) = ( + find(DataType::UserId), + find(DataType::SessionId), + find(DataType::IotaId), + ) + else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + (*user_id, *session_id, *iota_id) + } else { + let DataValue::Array(values) = item else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + let [ + DataValue::SignedNumber(user_id), + DataValue::SignedNumber(session_id), + DataValue::SignedNumber(iota_id), + ] = values.as_slice() + else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + (*user_id, *session_id, *iota_id) + }; + let (Ok(user_id), Ok(session_id), Ok(iota_id)) = ( + i64::try_from(user_id), + i64::try_from(session_id), + i64::try_from(iota_id), + ) else { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + }; + if user_id <= 0 || session_id <= 0 || iota_id <= 0 || !iota_ids.contains(&iota_id) { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + if sessions.iter().any(|(existing_user, existing_session, _)| { + *existing_user == user_id && *existing_session == session_id + }) { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + sessions.push((user_id, session_id, iota_id)); + } + + let previous_iota_ids = state.presence.iota_ids_owned_by(omikron_id); + let previous_session_user_ids = state + .presence + .sessions_owned_by(omikron_id) + .into_iter() + .map(|(user_id, _, _)| user_id) + .collect::>(); + let new_session_user_ids = sessions + .iter() + .map(|(user_id, _, _)| *user_id) + .collect::>(); + let affected_iota_ids = previous_iota_ids + .iter() + .chain(iota_ids.iter()) + .copied() + .collect::>(); + let users = user_repo::get_users_by_ids_and_iota_ids( + &previous_session_user_ids + .iter() + .chain(new_session_user_ids.iter()) + .copied() + .collect::>(), + &affected_iota_ids.iter().copied().collect::>(), + ) + .await?; + let user_ids = users.iter().map(|user| user.id.0).collect::>(); + apply_preferences( + &state, + user_repo::get_presence_preferences(&user_ids).await?, + ); + let before = states_for_users(&state, &users); + state + .presence + .replace_omikron_snapshot(omikron_id, &iota_ids, &sessions); + publish_changed_states(&state, &before, &users).await; + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(request_id)) + .await +} + +pub async fn omikron_disconnected(state: Arc, omikron_id: i64) { + let iota_ids = state.presence.iota_ids_owned_by(omikron_id); + let session_user_ids = state + .presence + .sessions_owned_by(omikron_id) + .into_iter() + .map(|(user_id, _, _)| user_id) + .collect::>(); + let users = match user_repo::get_users_by_ids_and_iota_ids(&session_user_ids, &iota_ids).await { + Ok(users) => users, + Err(error) => { + log_in!( + crate::util::logger::PrintType::General, + "Failed to load users before Omikron {} cleanup: {}", + omikron_id, + error + ); + return; + } + }; + let user_ids = users.iter().map(|user| user.id.0).collect::>(); + if let Err(error) = user_repo::get_presence_preferences(&user_ids) + .await + .map(|preferences| apply_preferences(&state, preferences)) + { + log_in!( + crate::util::logger::PrintType::General, + "Failed to load preferences before Omikron {} cleanup: {}", + omikron_id, + error + ); + return; + } + let before = states_for_users(&state, &users); + let removed = state.presence.remove_omikron(omikron_id); + debug_assert_eq!(removed.iota_ids, { + let mut ids = iota_ids.clone(); + ids.sort_unstable(); + ids.dedup(); + ids + }); + publish_changed_states(&state, &before, &users).await; +} + +#[cfg(test)] +mod tests { + use super::parse_subscription; + use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; + + fn request(user_ids: DataValue) -> CommunicationValue { + CommunicationValue::new(CommunicationType::StateSubscribe) + .with_sender(7) + .add_typed_default(DataType::SessionId, DataValue::SignedNumber(11)) + .add_typed_default(DataType::UserIds, user_ids) + } + + #[test] + fn subscription_parser_deduplicates_valid_targets() { + let parsed = parse_subscription(&request(DataValue::Array(vec![ + DataValue::SignedNumber(20), + DataValue::SignedNumber(21), + DataValue::SignedNumber(20), + ]))) + .unwrap(); + assert_eq!(parsed, (7, 11, vec![20, 21])); + } + + #[test] + fn subscription_parser_rejects_missing_or_malformed_fields() { + let missing_users = CommunicationValue::new(CommunicationType::StateSubscribe) + .with_sender(7) + .add_typed_default(DataType::SessionId, DataValue::SignedNumber(11)); + assert_eq!(parse_subscription(&missing_users), Err("user_ids")); + + let malformed_users = request(DataValue::Array(vec![DataValue::Str("bad".into())])); + assert_eq!(parse_subscription(&malformed_users), Err("user_ids")); + + let invalid_session = CommunicationValue::new(CommunicationType::StateSubscribe) + .with_sender(7) + .add_typed_default(DataType::SessionId, DataValue::SignedNumber(0)) + .add_typed_default( + DataType::UserIds, + DataValue::Array(vec![DataValue::SignedNumber(20)]), + ); + assert_eq!(parse_subscription(&invalid_session), Err("session_id")); + } + + #[test] + fn empty_subscription_is_valid_and_authoritative() { + let parsed = parse_subscription(&request(DataValue::Array(Vec::new()))).unwrap(); + assert_eq!(parsed, (7, 11, Vec::new())); } - Ok(()) } diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index be116b1..690a887 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -1,46 +1,146 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; -use crate::sql::{connection_status::UserStatus, user_online_tracker}; +use crate::db::user_repo; use mtp::{ codec::{CommunicationType, CommunicationValue, DataType, DataValue}, type_map::TypeMap, }; -use std::sync::Arc; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; + +async fn send_error( + connection: Arc, + request_id: u32, + error_type: CommunicationType, + session_id: Option, +) -> OmikronResult<()> { + let mut response = CommunicationValue::new(error_type).with_id(request_id); + if let Some(session_id) = session_id { + response = + response.add_typed_default(DataType::SessionId, DataValue::SignedNumber(session_id)); + } + connection.send(&response).await +} pub async fn get( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let state = connection.state(); + let legacy_peer = !connection.peer_capabilities().client_state_push_v1; let DataValue::Array(ids) = value.get_data(DataType::UserIds) else { - return Ok(()); + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + None, + ) + .await; }; + let session_id = value + .get_data(DataType::SessionId) + .as_number() + .filter(|id| *id > 0); + if session_id.is_none() && !legacy_peer { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + None, + ) + .await; + } let tm = TypeMap::latest(); - let states = ids - .iter() - .filter_map(|id| { - let DataValue::SignedNumber(id) = id else { - return None; - }; - let status = user_online_tracker::get_user_status(*id as i64) - .map(|status| { - if status.connection_type == UserStatus::user_invisible { - UserStatus::user_offline.to_string() - } else { - status.connection_type.to_string() - } - }) - .unwrap_or_else(|| UserStatus::iota_offline.to_string()); - let mut map = Vec::new(); - if let Some(kind) = DataType::UserId.try_to_id(&tm) { - map.push((kind, DataValue::SignedNumber((*id as i64).into()))); - } - if let Some(kind) = DataType::UserState.try_to_id(&tm) { - map.push((kind, DataValue::Str(status))); - } - Some(DataValue::Container(map)) - }) - .collect(); + let mut requested_user_ids = Vec::new(); + let mut requested_set = HashSet::new(); + for id in ids { + let DataValue::SignedNumber(id) = id else { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + session_id, + ) + .await; + }; + let Ok(user_id) = i64::try_from(*id) else { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + session_id, + ) + .await; + }; + if user_id <= 0 { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInvalidData, + session_id, + ) + .await; + } + if !requested_set.insert(user_id) { + continue; + } + requested_user_ids.push(user_id); + } + + let users = match user_repo::get_users_by_ids(&requested_user_ids).await { + Ok(users) => users, + Err(_) => { + return send_error( + connection, + value.get_id(), + CommunicationType::ErrorInternal, + session_id, + ) + .await; + } + }; + let users_by_id: HashMap<_, _> = users.into_iter().map(|user| (user.id.0, user)).collect(); + let mut states = Vec::new(); + let mut missing_user_ids = Vec::new(); + for user_id in requested_user_ids { + let Some(user) = users_by_id.get(&user_id) else { + missing_user_ids.push(user_id); + continue; + }; + let status = state + .presence + .resolve_public_state(user_id, user.iota_id.0) + .to_string(); + let mut map = Vec::new(); + if let Some(kind) = DataType::UserId.try_to_id(&tm) { + map.push((kind, DataValue::SignedNumber(user_id.into()))); + } + if let Some(kind) = DataType::UserState.try_to_id(&tm) { + map.push((kind, DataValue::Str(status))); + } + states.push(DataValue::Container(map)); + } let response = CommunicationValue::new(CommunicationType::GetStates) .with_id(value.get_id()) .add_typed_default(DataType::UserStates, DataValue::Array(states)); + let response = if let Some(session_id) = session_id { + response.add_typed_default(DataType::SessionId, DataValue::SignedNumber(session_id)) + } else { + response + }; + let response = if legacy_peer { + response + } else { + response.add_typed_default( + DataType::MissingUserIds, + DataValue::Array( + missing_user_ids + .into_iter() + .map(|id| DataValue::SignedNumber(id.into())) + .collect(), + ), + ) + }; connection.send(&response).await } diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index 27c9dbb..7e79bee 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -2,7 +2,6 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, - sql::{connection_status::UserStatus, user_online_tracker}, }; use base64::{Engine as _, engine::general_purpose::STANDARD}; use mtp::{ @@ -11,9 +10,12 @@ use mtp::{ }; use std::sync::Arc; -fn connections(iota_id: i64) -> DataValue { +fn connections(connection: &OmikronConnection, iota_id: i64) -> DataValue { DataValue::Array( - user_online_tracker::get_iota_omikron_connections(iota_id) + connection + .state() + .presence + .iota_connections(iota_id) .unwrap_or_default() .into_iter() .map(|id| DataValue::SignedNumber(id.into())) @@ -25,6 +27,7 @@ pub async fn get_user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let state = connection.state(); let user = if let Some(id) = value.get_data(DataType::UserId).as_number() { user_repo::get_by_user_id(UserId::from(id as i64)) .await @@ -74,28 +77,36 @@ pub async fn get_user( response = response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar))); } - let online = user_online_tracker::get_user_status(id); + let route = state.presence.user_route(id); + let private_request = value.get_sender() as i64 == id; + let resolved_status = if private_request { + if !state + .presence + .load_preference(id, &user.presence_preference) + { + crate::log_in!( + crate::util::logger::PrintType::General, + "Invalid persisted presence preference for user {}, using user_online", + id + ); + } + state.presence.resolve_private_state(id) + } else { + state.presence.resolve_public_state(id, iota_id) + }; response = response .add_typed_default( DataType::OnlineStatus, - DataValue::Str( - online - .as_ref() - .map(|status| { - if status.connection_type == UserStatus::user_invisible { - UserStatus::user_offline.to_string() - } else { - status.connection_type.to_string() - } - }) - .unwrap_or_else(|| UserStatus::iota_offline.to_string()), - ), + DataValue::Str(resolved_status.to_string()), ) - .add_typed_default(DataType::OmikronConnections, connections(iota_id)); - if let Some(status) = online { + .add_typed_default( + DataType::OmikronConnections, + connections(&connection, iota_id), + ); + if let Some(route) = route { response = response.add_typed_default( DataType::OmikronId, - DataValue::SignedNumber(status.omikron_id.into()), + DataValue::SignedNumber(route.omikron_id.into()), ); } connection.send(&response).await @@ -147,7 +158,7 @@ pub async fn get_iota( .with_id(value.get_id()) .add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64())) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::OmikronConnections, connections(id)); + .add_typed_default(DataType::OmikronConnections, connections(&connection, id)); if let Some(user_id) = user_id { response = response.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())); diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 8171bf2..52c5fe8 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -1,3 +1,4 @@ +pub mod capabilities; pub mod connection; pub mod handlers; pub mod omikron_connection; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index e86f4a2..5001ff3 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,6 +1,8 @@ +use super::capabilities::{OmegaCapabilities, PeerCapabilities}; use crate::models::OmikronId; use crate::{ load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server, + state::OmegaState, transport::omikron_manager, util::{file_util::load_file_vec, logger::PrintType}, }; @@ -48,9 +50,11 @@ pub struct WaitingTask { pub struct OmikronConnection { id: u64, + state: Arc, sender: Mutex>, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, + peer_capabilities: PeerCapabilities, } impl Drop for OmikronConnection { fn drop(&mut self) { @@ -61,13 +65,26 @@ impl Drop for OmikronConnection { } impl OmikronConnection { - pub fn new(sender: WebMtpSender, id: u64) -> Arc { - Arc::new(Self { + pub fn new( + sender: WebMtpSender, + id: u64, + description: Option<&str>, + state: Arc, + ) -> Option> { + let peer_capabilities = + PeerCapabilities::from_identification_description(description).ok()?; + Some(Arc::new(Self { id, + state, sender: Mutex::new(Some(sender)), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), - }) + peer_capabilities, + })) + } + + pub fn peer_capabilities(&self) -> &PeerCapabilities { + &self.peer_capabilities } pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { @@ -76,6 +93,23 @@ impl OmikronConnection { PrintType::Omega, "Omikron connection started" ); + let capabilities = CommunicationValue::new(CommunicationType::IdentificationResponse) + .add_typed_default( + mtp::codec::DataType::Description, + mtp::codec::DataValue::Str( + OmegaCapabilities::current().identification_description(), + ), + ); + if let Err(error) = self.clone().send(&capabilities).await { + log_err!( + self.id as i64, + PrintType::Omega, + "Failed to send Omega capabilities: {}", + error + ); + self.clone().cleanup().await; + return; + } let cleanup_conn = self.clone(); *self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); @@ -132,27 +166,30 @@ impl OmikronConnection { async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { let id = self.id as i64; + let state = self.state.clone(); match value.get_comm_type_enum() { Some(CommunicationType::ShortenLink) => { crate::transport::handlers::links::shorten(self, value).await } Some(CommunicationType::UserConnected) => { - crate::transport::handlers::presence::user_connected(self, value, id).await + crate::transport::handlers::presence::user_connected(state, self, value, id).await } Some(CommunicationType::UserDisconnected) => { - crate::transport::handlers::presence::user_disconnected(self, value, id).await + crate::transport::handlers::presence::user_disconnected(state, self, value, id) + .await } - Some(CommunicationType::ClientChanged) => { - crate::transport::handlers::presence::client_changed(self, value, id).await + Some(CommunicationType::SetUserState) => { + crate::transport::handlers::presence::set_user_state(state, self, value, id).await } Some(CommunicationType::IotaConnected) => { - crate::transport::handlers::presence::iota_connected(self, value, id).await + crate::transport::handlers::presence::iota_connected(state, self, value, id).await } Some(CommunicationType::IotaDisconnected) => { - crate::transport::handlers::presence::iota_disconnected(self, value, id).await + crate::transport::handlers::presence::iota_disconnected(state, self, value, id) + .await } Some(CommunicationType::SyncClientIotaStatus) => { - crate::transport::handlers::presence::sync_status(self, value, id).await + crate::transport::handlers::presence::sync_status(state, self, value, id).await } Some(CommunicationType::GetUserData) => { crate::transport::handlers::user_data::get_user(self, value).await @@ -193,6 +230,13 @@ impl OmikronConnection { Some(CommunicationType::GetStates) => { crate::transport::handlers::states::get(self, value).await } + Some(CommunicationType::StateSubscribe) => { + crate::transport::handlers::presence::state_subscribe(state, self, value, id).await + } + Some(CommunicationType::ClientChanged) => { + crate::transport::handlers::presence::client_changed_legacy(state, self, value, id) + .await + } _ => { log_err!( 0, @@ -216,6 +260,24 @@ impl OmikronConnection { .await .map_err(|error| crate::error::OmegaError::SendError(error.to_string())) } + + pub(crate) async fn send_messages( + self: Arc, + values: &[CommunicationValue], + ) -> OmikronResult<()> { + let guard = self.sender.lock().await; + let sender = guard + .as_ref() + .ok_or(crate::error::OmegaError::NotConnected)?; + for value in values { + log_cv_out!(PrintType::Omikron, value); + sender + .send(value) + .await + .map_err(|error| crate::error::OmegaError::SendError(error.to_string()))?; + } + Ok(()) + } pub(crate) async fn send_error_response( self: Arc, message_id: u32, @@ -224,6 +286,22 @@ impl OmikronConnection { self.send(&CommunicationValue::new(error_type).with_id(message_id)) .await } + pub(crate) async fn send_error_response_with_detail( + self: Arc, + message_id: u32, + error_type: CommunicationType, + detail: &'static str, + ) -> OmikronResult<()> { + self.send( + &CommunicationValue::new(error_type) + .with_id(message_id) + .add_typed_default( + mtp::codec::DataType::ErrorType, + mtp::codec::DataValue::Str(detail.to_string()), + ), + ) + .await + } pub async fn close(self: Arc) { log_in!( self.id as i64, @@ -238,7 +316,11 @@ impl OmikronConnection { if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); if omikron_manager::remove_omikron(self.id as i64, &self).await { - crate::sql::user_online_tracker::untrack_omikron(self.id as i64).await; + crate::transport::handlers::presence::omikron_disconnected( + self.state.clone(), + self.id as i64, + ) + .await; } } if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { @@ -248,12 +330,19 @@ impl OmikronConnection { pub async fn get_omikron_id(self: Arc) -> Option { Some(self.id as i64) } + pub fn state(&self) -> Arc { + self.state.clone() + } pub async fn send_message(self: Arc, value: &CommunicationValue) -> OmikronResult<()> { self.send(value).await } } -pub async fn get_by_omikron_id(omikron_id: u64, _: Option) -> Option { +pub async fn get_by_omikron_id( + omikron_id: u64, + description: Option, +) -> Option { + PeerCapabilities::from_identification_description(description.as_deref()).ok()?; crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64)) .await .ok() @@ -263,7 +352,7 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { 0 } -pub async fn start(port: u16) -> Result<(), Box> { +pub async fn start(port: u16, state: Arc) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "cert.pem")?; let key_pem = load_file_vec("certs", "key.pem")?; let web_config = server::server::build_web_config()? @@ -331,7 +420,19 @@ pub async fn start(port: u16) -> Result<(), Box> { ); continue; } - let connection = OmikronConnection::new(conn.sender, conn.client_id); + let Some(connection) = OmikronConnection::new( + conn.sender, + conn.client_id, + conn.description.as_deref(), + state.clone(), + ) else { + log_err!( + 0, + PrintType::Omega, + "Rejected Omikron connection with invalid capabilities" + ); + continue; + }; tokio::spawn(async move { let _guard = ConnectionLimitGuard(peer_ip); omikron_manager::add_omikron(connection.clone()).await; diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 0e1575d..254ced8 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -1,4 +1,7 @@ +use crate::db::user_repo; +use crate::state::OmegaState; use crate::transport::connection::OmikronConnection; +use crate::transport::omikron_connection::OmikronResult; use dashmap::DashMap; use mtp::codec::CommunicationValue; use once_cell::sync::Lazy; @@ -34,6 +37,61 @@ pub fn get_connected_omikron(omikron_id: i64) -> Option> .map(|connection| connection.clone()) } +pub fn get_state() -> Option> { + OMIKRON_CONNECTIONS + .iter() + .next() + .map(|connection| connection.value().state()) +} + +pub fn get_iota_primary_omikron_connection(iota_id: i64) -> Option { + get_state().and_then(|state| state.presence.primary_iota_route(iota_id)) +} + +pub async fn get_all_connections() +-> Result>>, ()> { + match get_state() { + Some(state) => { + let mut result = state.presence.connection_routes(); + let iota_ids = state + .presence + .all_iota_routes() + .keys() + .copied() + .collect::>(); + let users = user_repo::get_users_by_iota_ids(&iota_ids) + .await + .map_err(|_| ())?; + for user in users { + for route in state.presence.routes_for_user(user.id.0) { + if let Some(iotas) = result.get_mut(&route.omikron_id) { + if let Some(users) = iotas.get_mut(&user.iota_id.0) { + users.push(user.id.0); + } + } + } + } + for iotas in result.values_mut() { + for users in iotas.values_mut() { + users.sort_unstable(); + users.dedup(); + } + } + Ok(result) + } + None => Ok(std::collections::HashMap::new()), + } +} + +pub async fn send_state_batch( + omikron_id: i64, + notifications: Vec, +) -> OmikronResult<()> { + let connection = + get_connected_omikron(omikron_id).ok_or(crate::error::OmegaError::NotConnected)?; + connection.send_messages(¬ifications).await +} + pub async fn get_random_omikron() -> Result, ()> { let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); @@ -47,9 +105,11 @@ pub async fn get_random_omikron() -> Result, ()> { } pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) { - if let Some(user_conn) = crate::sql::user_online_tracker::get_user_status(user_id) { - if let Some(omikron_conn) = OMIKRON_CONNECTIONS.get(&user_conn.omikron_id) { - let _ = omikron_conn.value().clone().send_message(cv).await; + if let Some(state) = get_state() { + for user_route in state.presence.routes_for_user(user_id) { + if let Some(omikron_conn) = OMIKRON_CONNECTIONS.get(&user_route.omikron_id) { + let _ = omikron_conn.value().clone().send_message(cv).await; + } } } } From d3bcf56b59915d81234da62bc4b9a5a9f47cbfb5 Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 9 Aug 2026 02:50:54 +0200 Subject: [PATCH 211/220] [Fix] User deletion & migration --- Cargo.lock | 228 ++++++++++--------- migrations/005_nullable_user_iota.sql | 11 + migrations/006_pending_iota_user_erasure.sql | 8 + src/api/response.rs | 4 +- src/db/user_repo.rs | 46 +++- src/models/user.rs | 2 +- src/server/api.rs | 14 +- src/state.rs | 29 +++ src/transport/handlers/account.rs | 165 +++++++++++++- src/transport/handlers/presence.rs | 7 +- src/transport/handlers/states.rs | 2 +- src/transport/handlers/user_data.rs | 41 ++-- src/transport/omikron_connection.rs | 19 ++ src/transport/omikron_manager.rs | 30 ++- 14 files changed, 457 insertions(+), 149 deletions(-) create mode 100644 migrations/005_nullable_user_iota.sql create mode 100644 migrations/006_pending_iota_user_erasure.sql diff --git a/Cargo.lock b/Cargo.lock index 34e4f21..1e03155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,7 +56,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.19", + "thiserror", "time", ] @@ -117,9 +117,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.3" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" dependencies = [ "cc", "cmake", @@ -145,6 +145,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -217,9 +223,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136" dependencies = [ "find-msvc-tools", "jobserver", @@ -467,8 +473,23 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", "rustc_version", "subtle", "zeroize", @@ -501,9 +522,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] name = "deflate64" @@ -518,7 +539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -529,6 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid 0.10.2", + "pem-rfc7468 1.0.0", "zeroize", ] @@ -608,24 +630,25 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "ed25519" -version = "2.2.3" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", + "pkcs8 0.11.0", + "signature 3.0.0", ] [[package]] name = "ed25519-dalek" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 5.0.0", "ed25519", "serde", - "sha2 0.10.9", + "sha2 0.11.0", + "signature 3.0.0", "subtle", "zeroize", ] @@ -710,10 +733,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "fiat-crypto" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "flate2" @@ -1077,9 +1106,9 @@ checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1122,9 +1151,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "ctutils", "typenum", @@ -1173,7 +1202,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -1325,9 +1354,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "itoa" @@ -1347,7 +1376,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror", "walkdir", "windows-link", ] @@ -1416,9 +1445,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -1453,9 +1482,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "bitflags", "libc", @@ -1614,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1659,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,9 +1672,9 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ - "base64", + "base64 0.23.1", "byteorder", "mtp-common", "mtp-crypto", @@ -1656,20 +1685,20 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "quinn", "rustls", - "thiserror 2.0.19", + "thiserror", "wtransport", ] [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ - "base64", + "base64 0.23.1", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", @@ -1681,7 +1710,7 @@ dependencies = [ "rustls", "serde", "sha2 0.11.0", - "thiserror 1.0.69", + "thiserror", "tokio", "zeroize", ] @@ -1689,24 +1718,24 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-crypto", "rand 0.10.2", - "thiserror 1.0.69", + "thiserror", "zeroize", ] [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.8.7", + "rand 0.10.2", "tokio", "tracing", "wtransport", @@ -1715,7 +1744,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1762,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1771,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "async-trait", "bytes", @@ -1761,7 +1790,7 @@ dependencies = [ "quinn", "rand 0.10.2", "rustls", - "thiserror 2.0.19", + "thiserror", "tokio", "tokio-rustls", "tokio-stream", @@ -1859,7 +1888,7 @@ name = "omega" version = "0.1.0" dependencies = [ "ansi_term", - "base64", + "base64 0.22.1", "bytes", "dashmap", "dotenv", @@ -1874,7 +1903,7 @@ dependencies = [ "sqlx", "strum", "strum_macros", - "thiserror 2.0.19", + "thiserror", "tokio", "uuid", "zip", @@ -1943,7 +1972,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -1956,6 +1985,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2082,7 +2120,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.19", + "thiserror", "tokio", "tracing", "web-time", @@ -2107,7 +2145,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror 2.0.19", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2237,7 +2275,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -2329,9 +2367,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -2598,7 +2636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" dependencies = [ "digest 0.11.3", - "keccak 0.2.0", + "keccak 0.2.1", "sponge-cursor", ] @@ -2745,7 +2783,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "crc", "crossbeam-queue", @@ -2766,7 +2804,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.19", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -2818,7 +2856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "bytes", @@ -2848,7 +2886,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.19", + "thiserror", "tracing", "whoami", ] @@ -2860,7 +2898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "crc", @@ -2885,7 +2923,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.19", + "thiserror", "tracing", "whoami", ] @@ -2909,7 +2947,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.19", + "thiserror", "tracing", "url", ] @@ -3018,33 +3056,13 @@ dependencies = [ "libc", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] @@ -3060,9 +3078,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "js-sys", @@ -3133,13 +3151,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -3515,7 +3533,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -3728,7 +3746,7 @@ dependencies = [ "rustls-pki-types", "sha2 0.11.0", "socket2", - "thiserror 2.0.19", + "thiserror", "time", "tokio", "tracing", @@ -3745,7 +3763,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror 2.0.19", + "thiserror", "url", ] @@ -3755,7 +3773,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", @@ -3776,7 +3794,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.19", + "thiserror", "time", ] @@ -3815,18 +3833,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -3936,9 +3954,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" [[package]] name = "zmij" diff --git a/migrations/005_nullable_user_iota.sql b/migrations/005_nullable_user_iota.sql new file mode 100644 index 0000000..66e0edb --- /dev/null +++ b/migrations/005_nullable_user_iota.sql @@ -0,0 +1,11 @@ +ALTER TABLE users + DROP FOREIGN KEY fk_users_iota; + +ALTER TABLE users + MODIFY iota_id BIGINT NULL; + +ALTER TABLE users + ADD CONSTRAINT fk_users_iota + FOREIGN KEY (iota_id) + REFERENCES iotas (id) + ON DELETE SET NULL; diff --git a/migrations/006_pending_iota_user_erasure.sql b/migrations/006_pending_iota_user_erasure.sql new file mode 100644 index 0000000..eb72c7b --- /dev/null +++ b/migrations/006_pending_iota_user_erasure.sql @@ -0,0 +1,8 @@ +CREATE TABLE pending_iota_user_erasure ( + user_id BIGINT NOT NULL, + iota_id BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, iota_id), + CONSTRAINT fk_pending_iota_user_erasure_iota + FOREIGN KEY (iota_id) REFERENCES iotas(id) ON DELETE CASCADE +); diff --git a/src/api/response.rs b/src/api/response.rs index e8489b5..965d85e 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -28,7 +28,7 @@ pub struct UserResponse { pub username: String, pub public_key: String, pub user_id: i64, - pub iota_id: i64, + pub iota_id: Option, pub sub_level: i32, pub sub_end: i64, #[serde(skip_serializing_if = "Option::is_none")] @@ -47,7 +47,7 @@ pub struct UsernameResponse { pub username: String, pub public_key: String, pub user_id: i64, - pub iota_id: i64, + pub iota_id: Option, pub sub_level: i32, pub sub_end: i64, } diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index 01b0e6e..df99a18 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -101,7 +101,7 @@ const USER_COLUMNS: &str = "SELECT id, iota_id, username, display, status, prese #[derive(FromRow)] struct UserRow { id: i64, - iota_id: i64, + iota_id: Option, username: Vec, display: Option>, status: Option>, @@ -124,7 +124,7 @@ impl TryFrom for User { |value| String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error))); Ok(User { id: row.id.into(), - iota_id: row.iota_id.into(), + iota_id: row.iota_id.map(IotaId::from), username: decode(row.username)?, display: row.display.map(decode).transpose()?, status: row.status.map(decode).transpose()?, @@ -344,9 +344,9 @@ pub async fn change_presence_preference(id: UserId, value: String) -> Result<()> .await } -pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> { +pub async fn change_iota_id(id: UserId, value: Option) -> Result<()> { sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") - .bind(value.0) + .bind(value.map(|id| id.0)) .bind(id.0) .execute(&pool().await?) .await?; @@ -368,6 +368,42 @@ pub async fn delete_user(id: UserId) -> Result<()> { Ok(()) } +/// Delete the central identity while retaining a durable instruction for the +/// last hosting Iota. The pending row is intentionally independent of users: +/// it must outlive the account row. +pub async fn delete_user_with_pending_erasure(id: UserId) -> Result> { + let mut tx = pool().await?.begin().await?; + let row = sqlx::query("SELECT iota_id FROM users WHERE id = ? FOR UPDATE") + .bind(id.0) + .fetch_optional(&mut *tx) + .await? + .ok_or(OmegaError::NotFound)?; + let iota_id: Option = row.get("iota_id"); + if let Some(iota_id) = iota_id { + sqlx::query("INSERT IGNORE INTO pending_iota_user_erasure (user_id, iota_id) VALUES (?, ?)") + .bind(id.0) + .bind(iota_id) + .execute(&mut *tx) + .await?; + } + sqlx::query("DELETE FROM registration_leases WHERE user_id = ?").bind(id.0).execute(&mut *tx).await?; + sqlx::query("DELETE FROM users WHERE id = ?").bind(id.0).execute(&mut *tx).await?; + tx.commit().await?; + Ok(iota_id.map(IotaId::from)) +} + +pub async fn pending_erasures_for_iota(iota_id: IotaId) -> Result> { + let rows = sqlx::query("SELECT user_id FROM pending_iota_user_erasure WHERE iota_id = ?") + .bind(iota_id.0).fetch_all(&pool().await?).await?; + Ok(rows.into_iter().map(|row| UserId::from(row.get::("user_id"))).collect()) +} + +pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Result { + let result = sqlx::query("DELETE FROM pending_iota_user_erasure WHERE user_id = ? AND iota_id = ?") + .bind(user_id.0).bind(iota_id.0).execute(&pool().await?).await?; + Ok(result.rows_affected() == 1) +} + pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> { sqlx::query("UPDATE users SET public_key = ? WHERE id = ?") .bind(public_key.as_bytes()) @@ -438,7 +474,7 @@ pub async fn register_complete_user( .map_err(OmegaError::from)? { Some(existing) - if existing.iota_id == iota_id + if existing.iota_id == Some(iota_id) && existing.username == username && existing.public_key.as_bytes() == public_key.as_bytes() && existing.token == token => diff --git a/src/models/user.rs b/src/models/user.rs index 2843b6d..a9595d9 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -4,7 +4,7 @@ use mtp::crypto::PublicKeyBundle; #[derive(Clone, Debug, serde::Serialize)] pub struct User { pub id: UserId, - pub iota_id: IotaId, + pub iota_id: Option, pub username: String, pub display: Option, pub status: Option, diff --git a/src/server/api.rs b/src/server/api.rs index 498685a..0319607 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -41,7 +41,7 @@ fn user_response(user: crate::models::User) -> UserResponse { username: user.username, public_key: user.public_key.to_base64(), user_id: user.id.0, - iota_id: user.iota_id.0, + iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, sub_end: user.sub_end, display: user.display, @@ -83,7 +83,15 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { omikron_id } else { let user = get_by_user_id(UserId::from(id)).await?; - get_iota_primary_omikron_connection(user.iota_id.0).ok_or(OmegaError::NotFound)? + match user.iota_id { + Some(iota_id) => get_iota_primary_omikron_connection(iota_id.0), + None => get_random_omikron() + .await + .map_err(|_| OmegaError::NotFound)? + .get_omikron_id() + .await, + } + .ok_or(OmegaError::NotFound)? }; // Database rows describe registered Omikrons. The public discovery @@ -150,7 +158,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { username: user.username, public_key: user.public_key.to_base64(), user_id: user.id.0, - iota_id: user.iota_id.0, + iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, sub_end: user.sub_end, }), diff --git a/src/state.rs b/src/state.rs index dc91021..075d268 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,18 +1,47 @@ use crate::sql::user_online_tracker::PresenceTracker; use std::sync::Arc; +use dashmap::DashMap; +use std::time::{Duration, Instant}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AccountChallengeOperation { Attach, Delete } + +#[derive(Clone, Debug)] +pub struct AccountChallenge { + pub operation: AccountChallengeOperation, + pub user_id: i64, + pub requester_iota_id: i64, + pub nonce: u64, + pub created_at: Instant, +} pub struct OmegaState { pub presence: Arc, + challenges: DashMap<(AccountChallengeOperation, i64, i64), AccountChallenge>, } impl Default for OmegaState { fn default() -> Self { Self { presence: Arc::new(PresenceTracker::default()), + challenges: DashMap::new(), } } } +impl OmegaState { + pub fn issue_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64) -> u64 { + let nonce = rand::random::(); + self.challenges.insert((operation, user_id, requester_iota_id), AccountChallenge { operation, user_id, requester_iota_id, nonce, created_at: Instant::now() }); + nonce + } + + pub fn consume_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64, nonce: u64) -> bool { + self.challenges.remove(&(operation, user_id, requester_iota_id)).is_some_and(|(_, value)| + value.nonce == nonce && value.created_at.elapsed() <= Duration::from_secs(120)) + } +} + impl OmegaState { pub fn new() -> Arc { Arc::new(Self::default()) diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs index 1927b14..360076d 100644 --- a/src/transport/handlers/account.rs +++ b/src/transport/handlers/account.rs @@ -2,8 +2,9 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, + state::AccountChallengeOperation, }; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, crypto::{verify_ed25519, verify_ml_dsa}}; use std::sync::Arc; async fn delete( @@ -23,12 +24,8 @@ pub async fn user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - delete( - connection, - value.clone(), - user_repo::delete_user(UserId::from(value.get_sender() as i64)), - ) - .await + let user_id = UserId::from(value.get_sender() as i64); + complete_delete(connection, value, user_id).await } pub async fn iota( connection: Arc, @@ -41,3 +38,157 @@ pub async fn iota( ) .await } + +pub async fn release_from_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; + let requester = IotaId::from(value.get_sender() as i64); + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; + if user.iota_id != Some(requester) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await; + } + let previous_iota = user.iota_id; + match user_repo::change_iota_id(user.id, None).await { + Ok(()) => { + if let Some(iota) = previous_iota { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } + connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await + }, + Err(error) => connection + .send(&CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))) + .await, + } +} + +fn lifecycle_payload(domain: &[u8], user_id: i64, iota_id: i64, nonce: u64) -> Vec { + let mut payload = Vec::with_capacity(domain.len() + 32); + payload.extend_from_slice(domain); + payload.extend_from_slice(&user_id.to_be_bytes()); + payload.extend_from_slice(&iota_id.to_be_bytes()); + payload.extend_from_slice(&nonce.to_be_bytes()); + payload +} + +pub async fn attach_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { + return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; + }; + if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { + return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; + } + let requester = value.get_sender() as i64; + let nonce = connection.state().issue_challenge(AccountChallengeOperation::Attach, user_id, requester); + connection.send(&CommunicationValue::new(CommunicationType::AttachUserChallenge).with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await +} + +pub async fn attach_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; + let requester = value.get_sender() as i64; + let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + let signature = value.get_data(DataType::Signature).as_bytes(); + let pq_signature = value.get_data(DataType::PqSignature).as_bytes(); + let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + if !connection.state().consume_challenge(AccountChallengeOperation::Attach, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce); + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + let previous_iota = user.iota_id; + match user_repo::change_iota_id(user.id, Some(IotaId::from(requester))).await { + Ok(()) => { + if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } + crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; + connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await + }, + Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + } +} + +async fn complete_delete(connection: Arc, value: CommunicationValue, user_id: UserId) -> OmikronResult<()> { + match user_repo::delete_user_with_pending_erasure(user_id).await { + Ok(iota_id) => { + let cleanup_pending = iota_id.is_some(); + if let Some(iota_id) = iota_id { + crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id.0).await; + crate::transport::omikron_manager::deliver_pending_erasures(iota_id.0).await; + } + connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()) + .add_typed_default(DataType::CleanupPending, DataValue::Bool(cleanup_pending))).await + } + Err(crate::error::OmegaError::NotFound) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await, + Err(error) => connection.send(&CommunicationValue::new(CommunicationType::ErrorInternal).with_id(value.get_id()).add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))).await, + } +} + +pub async fn delete_credential_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { + return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; + }; + if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; } + let requester = value.get_sender() as i64; + let nonce = connection.state().issue_challenge(AccountChallengeOperation::Delete, user_id, requester); + connection.send(&CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge).with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await +} + +pub async fn delete_credential_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; + let requester = value.get_sender() as i64; + let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + let (Some(signature), Some(pq_signature)) = (value.get_data(DataType::Signature).as_bytes(), value.get_data(DataType::PqSignature).as_bytes()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + if !connection.state().consume_challenge(AccountChallengeOperation::Delete, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce); + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + complete_delete(connection, value, user.id).await +} + +pub async fn erase_hosted_user_data_ack(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; + let iota_id = IotaId::from(value.get_sender() as i64); + match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await { + Ok(true) => connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await, + Ok(false) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await, + Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + } +} + +/// New lifecycle operation names are intentionally fail-closed until their +/// proof and durable-erasure handlers are enabled. This explicit dispatch +/// prevents either a bare Iota request or the legacy DeleteUser path from +/// acquiring account-deletion authority during a staged rollout. +pub async fn lifecycle_unavailable( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorNotAuthenticated) + .with_id(value.get_id()) + .add_typed_default( + DataType::ErrorType, + DataValue::Str("user lifecycle proof handler is not enabled".into()), + ), + ) + .await +} diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index 0c31904..de8a275 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -53,7 +53,7 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa user.id.0, state .presence - .resolve_public_state(user.id.0, user.iota_id.0), + .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()), ) }) .collect() @@ -69,7 +69,7 @@ fn changed_states( .filter_map(|user| { let after = state .presence - .resolve_public_state(user.id.0, user.iota_id.0); + .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()); (before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after)) }) .collect::>(); @@ -275,7 +275,7 @@ pub async fn user_connected( Ok(preferences) => preferences, Err(error) => return Err(error.into()), }; - if user.iota_id.0 != iota_id || !state.presence.has_iota_route(iota_id) { + if user.iota_id.map(|id| id.0) != Some(iota_id) || !state.presence.has_iota_route(iota_id) { return connection .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) .await; @@ -497,6 +497,7 @@ pub async fn iota_connected( .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); connection.clone().send(&response).await?; + crate::transport::omikron_manager::deliver_pending_erasures(iota_id).await; publish_changed_states(&state, &before, &users).await; connection .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index 690a887..d147f27 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -110,7 +110,7 @@ pub async fn get( }; let status = state .presence - .resolve_public_state(user_id, user.iota_id.0) + .resolve_public_state(user_id, user.iota_id.map(|id| id.0).unwrap_or_default()) .to_string(); let mut map = Vec::new(); if let Some(kind) = DataType::UserId.try_to_id(&tm) { diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index 7e79bee..e331125 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -43,7 +43,7 @@ pub async fn get_user( .await; }; let id = user.id.0; - let iota_id = user.iota_id.0; + let iota_id = user.iota_id.map(|id| id.0); let username = user.username.clone(); let display = user .display @@ -57,7 +57,6 @@ pub async fn get_user( DataValue::Str(user.public_key.to_base64()), ) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::Display, DataValue::Str(display)) .add_typed_default( DataType::SubLevel, @@ -92,7 +91,7 @@ pub async fn get_user( } state.presence.resolve_private_state(id) } else { - state.presence.resolve_public_state(id, iota_id) + iota_id.map(|iota_id| state.presence.resolve_public_state(id, iota_id)).unwrap_or(crate::sql::connection_status::UserStatus::user_offline) }; response = response .add_typed_default( @@ -101,8 +100,11 @@ pub async fn get_user( ) .add_typed_default( DataType::OmikronConnections, - connections(&connection, iota_id), + iota_id.map(|iota_id| connections(&connection, iota_id)).unwrap_or_else(|| DataValue::Array(Vec::new())), ); + if let Some(iota_id) = iota_id { + response = response.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); + } if let Some(route) = route { response = response.add_typed_default( DataType::OmikronId, @@ -123,26 +125,25 @@ pub async fn get_iota( .map(|iota| (iota.id.0, iota.public_key, None, None)) } else if let Some(id) = value.get_data(DataType::UserId).as_number() { if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await { - iota_repo::get_iota_by_id(user.iota_id) - .await - .ok() - .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)) + match user.iota_id { + Some(iota_id) => iota_repo::get_iota_by_id(iota_id) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)), + None => None, + } } else { None } } else if let Some(name) = value.get_data(DataType::Username).as_str() { if let Ok(user) = user_repo::get_by_username(name).await { - iota_repo::get_iota_by_id(user.iota_id) - .await - .ok() - .map(|iota| { - ( - iota.id.0, - iota.public_key, - Some(user.id.0), - Some(name.to_owned()), - ) - }) + match user.iota_id { + Some(iota_id) => iota_repo::get_iota_by_id(iota_id) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), Some(name.to_owned()))), + None => None, + } } else { None } @@ -269,7 +270,7 @@ pub async fn change_iota( .await; } let result = - match user_repo::change_iota_id(user_id, IotaId::from(value.get_sender() as i64)).await { + match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))).await { Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, Err(error) => Err(error), }; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 5001ff3..7ad0136 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -87,6 +87,7 @@ impl OmikronConnection { &self.peer_capabilities } + pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, @@ -215,6 +216,24 @@ impl OmikronConnection { Some(CommunicationType::DeleteUser) => { crate::transport::handlers::account::user(self, value).await } + Some(CommunicationType::AttachUserBegin) => { + crate::transport::handlers::account::attach_begin(self, value).await + } + Some(CommunicationType::AttachUserComplete) => { + crate::transport::handlers::account::attach_complete(self, value).await + } + Some(CommunicationType::DeleteUserCredentialBegin) => { + crate::transport::handlers::account::delete_credential_begin(self, value).await + } + Some(CommunicationType::DeleteUserCredentialComplete) => { + crate::transport::handlers::account::delete_credential_complete(self, value).await + } + Some(CommunicationType::EraseHostedUserDataAck) => { + crate::transport::handlers::account::erase_hosted_user_data_ack(self, value).await + } + Some(CommunicationType::ReleaseUserFromIota) => { + crate::transport::handlers::account::release_from_iota(self, value).await + } Some(CommunicationType::DeleteIota) => { crate::transport::handlers::account::iota(self, value).await } diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 254ced8..9020556 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -3,7 +3,7 @@ use crate::state::OmegaState; use crate::transport::connection::OmikronConnection; use crate::transport::omikron_connection::OmikronResult; use dashmap::DashMap; -use mtp::codec::CommunicationValue; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; use std::sync::Arc; @@ -65,7 +65,8 @@ pub async fn get_all_connections() for user in users { for route in state.presence.routes_for_user(user.id.0) { if let Some(iotas) = result.get_mut(&route.omikron_id) { - if let Some(users) = iotas.get_mut(&user.iota_id.0) { + if let Some(iota_id) = user.iota_id + && let Some(users) = iotas.get_mut(&iota_id.0) { users.push(user.id.0); } } @@ -113,3 +114,28 @@ pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) { } } } + +/// Publish the authoritative membership list after an attach, migration, or +/// release. Omikron replaces its full local index from this snapshot. +pub async fn publish_iota_user_snapshot(iota_id: i64) { + let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) else { return; }; + let Some(connection) = get_connected_omikron(omikron_id) else { return; }; + let Ok(users) = user_repo::get_users_by_iota_id(crate::models::IotaId::from(iota_id)).await else { return; }; + let user_ids = users.into_iter().map(|user| DataValue::SignedNumber(user.id.0.into())).collect(); + let snapshot = CommunicationValue::new(CommunicationType::IotaUserData) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); + let _ = connection.send(&snapshot).await; +} + +pub async fn deliver_pending_erasures(iota_id: i64) { + let Ok(users) = user_repo::pending_erasures_for_iota(crate::models::IotaId::from(iota_id)).await else { return; }; + let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) else { return; }; + let Some(connection) = get_connected_omikron(omikron_id) else { return; }; + for user_id in users { + let request = CommunicationValue::new(CommunicationType::EraseHostedUserData) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.0.into())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); + let _ = connection.clone().send(&request).await; + } +} From 19d97e45556414692a415ad3f0a1b7e11165767c Mon Sep 17 00:00:00 2001 From: Alex Date: Sun, 9 Aug 2026 02:50:54 +0200 Subject: [PATCH 212/220] [Fix] User deletion & migration --- Cargo.lock | 228 ++++++++++--------- migrations/005_nullable_user_iota.sql | 11 + migrations/006_pending_iota_user_erasure.sql | 8 + src/api/response.rs | 4 +- src/db/user_repo.rs | 46 +++- src/models/user.rs | 2 +- src/server/api.rs | 14 +- src/state.rs | 29 +++ src/transport/handlers/account.rs | 165 +++++++++++++- src/transport/handlers/presence.rs | 7 +- src/transport/handlers/states.rs | 2 +- src/transport/handlers/user_data.rs | 41 ++-- src/transport/omikron_connection.rs | 19 ++ src/transport/omikron_manager.rs | 30 ++- 14 files changed, 457 insertions(+), 149 deletions(-) create mode 100644 migrations/005_nullable_user_iota.sql create mode 100644 migrations/006_pending_iota_user_erasure.sql diff --git a/Cargo.lock b/Cargo.lock index 34e4f21..1e03155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,7 +56,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.19", + "thiserror", "time", ] @@ -117,9 +117,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.17.3" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" +checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e" dependencies = [ "aws-lc-sys", "untrusted 0.7.1", @@ -128,9 +128,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.43.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" +checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483" dependencies = [ "cc", "cmake", @@ -145,6 +145,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -217,9 +223,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136" dependencies = [ "find-msvc-tools", "jobserver", @@ -467,8 +473,23 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", "rustc_version", "subtle", "zeroize", @@ -501,9 +522,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.11.0" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] name = "deflate64" @@ -518,7 +539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid 0.9.6", - "pem-rfc7468", + "pem-rfc7468 0.7.0", "zeroize", ] @@ -529,6 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ "const-oid 0.10.2", + "pem-rfc7468 1.0.0", "zeroize", ] @@ -608,24 +630,25 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] name = "ed25519" -version = "2.2.3" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", + "pkcs8 0.11.0", + "signature 3.0.0", ] [[package]] name = "ed25519-dalek" -version = "2.2.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 5.0.0", "ed25519", "serde", - "sha2 0.10.9", + "sha2 0.11.0", + "signature 3.0.0", "subtle", "zeroize", ] @@ -710,10 +733,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] -name = "find-msvc-tools" -version = "0.1.9" +name = "fiat-crypto" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" [[package]] name = "flate2" @@ -1077,9 +1106,9 @@ checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -1122,9 +1151,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hybrid-array" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "ctutils", "typenum", @@ -1173,7 +1202,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-channel", "futures-util", @@ -1325,9 +1354,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" [[package]] name = "itoa" @@ -1347,7 +1376,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror 2.0.19", + "thiserror", "walkdir", "windows-link", ] @@ -1416,9 +1445,9 @@ dependencies = [ [[package]] name = "keccak" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", @@ -1453,9 +1482,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" dependencies = [ "bitflags", "libc", @@ -1614,7 +1643,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-client", "mtp-codec", @@ -1630,7 +1659,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-codec", "mtp-common", @@ -1643,9 +1672,9 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ - "base64", + "base64 0.23.1", "byteorder", "mtp-common", "mtp-crypto", @@ -1656,20 +1685,20 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "quinn", "rustls", - "thiserror 2.0.19", + "thiserror", "wtransport", ] [[package]] name = "mtp-crypto" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ - "base64", + "base64 0.23.1", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", @@ -1681,7 +1710,7 @@ dependencies = [ "rustls", "serde", "sha2 0.11.0", - "thiserror 1.0.69", + "thiserror", "tokio", "zeroize", ] @@ -1689,24 +1718,24 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-crypto", "rand 0.10.2", - "thiserror 1.0.69", + "thiserror", "zeroize", ] [[package]] name = "mtp-host" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.8.7", + "rand 0.10.2", "tokio", "tracing", "wtransport", @@ -1715,7 +1744,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "async-trait", "mtp-codec", @@ -1733,7 +1762,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "serde", "serde_yaml", @@ -1742,7 +1771,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e" +source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" dependencies = [ "async-trait", "bytes", @@ -1761,7 +1790,7 @@ dependencies = [ "quinn", "rand 0.10.2", "rustls", - "thiserror 2.0.19", + "thiserror", "tokio", "tokio-rustls", "tokio-stream", @@ -1859,7 +1888,7 @@ name = "omega" version = "0.1.0" dependencies = [ "ansi_term", - "base64", + "base64 0.22.1", "bytes", "dashmap", "dotenv", @@ -1874,7 +1903,7 @@ dependencies = [ "sqlx", "strum", "strum_macros", - "thiserror 2.0.19", + "thiserror", "tokio", "uuid", "zip", @@ -1943,7 +1972,7 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] @@ -1956,6 +1985,15 @@ dependencies = [ "base64ct", ] +[[package]] +name = "pem-rfc7468" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -2082,7 +2120,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.19", + "thiserror", "tokio", "tracing", "web-time", @@ -2107,7 +2145,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror 2.0.19", + "thiserror", "tinyvec", "tracing", "web-time", @@ -2237,7 +2275,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", @@ -2329,9 +2367,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "aws-lc-rs", "log", @@ -2598,7 +2636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" dependencies = [ "digest 0.11.3", - "keccak 0.2.0", + "keccak 0.2.1", "sponge-cursor", ] @@ -2745,7 +2783,7 @@ version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "crc", "crossbeam-queue", @@ -2766,7 +2804,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.19", + "thiserror", "tokio", "tokio-stream", "tracing", @@ -2818,7 +2856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "bytes", @@ -2848,7 +2886,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.19", + "thiserror", "tracing", "whoami", ] @@ -2860,7 +2898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", - "base64", + "base64 0.22.1", "bitflags", "byteorder", "crc", @@ -2885,7 +2923,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.19", + "thiserror", "tracing", "whoami", ] @@ -2909,7 +2947,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.19", + "thiserror", "tracing", "url", ] @@ -3018,33 +3056,13 @@ dependencies = [ "libc", ] -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - [[package]] name = "thiserror" version = "2.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", + "thiserror-impl", ] [[package]] @@ -3060,9 +3078,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "js-sys", @@ -3133,13 +3151,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] @@ -3515,7 +3533,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -3728,7 +3746,7 @@ dependencies = [ "rustls-pki-types", "sha2 0.11.0", "socket2", - "thiserror 2.0.19", + "thiserror", "time", "tokio", "tracing", @@ -3745,7 +3763,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" dependencies = [ "httlib-huffman", "octets", - "thiserror 2.0.19", + "thiserror", "url", ] @@ -3755,7 +3773,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 4.1.3", "rand_core 0.6.4", "serde", "zeroize", @@ -3776,7 +3794,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror 2.0.19", + "thiserror", "time", ] @@ -3815,18 +3833,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.55" +version = "0.8.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", @@ -3936,9 +3954,9 @@ dependencies = [ [[package]] name = "zlib-rs" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" +checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" [[package]] name = "zmij" diff --git a/migrations/005_nullable_user_iota.sql b/migrations/005_nullable_user_iota.sql new file mode 100644 index 0000000..66e0edb --- /dev/null +++ b/migrations/005_nullable_user_iota.sql @@ -0,0 +1,11 @@ +ALTER TABLE users + DROP FOREIGN KEY fk_users_iota; + +ALTER TABLE users + MODIFY iota_id BIGINT NULL; + +ALTER TABLE users + ADD CONSTRAINT fk_users_iota + FOREIGN KEY (iota_id) + REFERENCES iotas (id) + ON DELETE SET NULL; diff --git a/migrations/006_pending_iota_user_erasure.sql b/migrations/006_pending_iota_user_erasure.sql new file mode 100644 index 0000000..eb72c7b --- /dev/null +++ b/migrations/006_pending_iota_user_erasure.sql @@ -0,0 +1,8 @@ +CREATE TABLE pending_iota_user_erasure ( + user_id BIGINT NOT NULL, + iota_id BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (user_id, iota_id), + CONSTRAINT fk_pending_iota_user_erasure_iota + FOREIGN KEY (iota_id) REFERENCES iotas(id) ON DELETE CASCADE +); diff --git a/src/api/response.rs b/src/api/response.rs index e8489b5..965d85e 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -28,7 +28,7 @@ pub struct UserResponse { pub username: String, pub public_key: String, pub user_id: i64, - pub iota_id: i64, + pub iota_id: Option, pub sub_level: i32, pub sub_end: i64, #[serde(skip_serializing_if = "Option::is_none")] @@ -47,7 +47,7 @@ pub struct UsernameResponse { pub username: String, pub public_key: String, pub user_id: i64, - pub iota_id: i64, + pub iota_id: Option, pub sub_level: i32, pub sub_end: i64, } diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index 01b0e6e..df99a18 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -101,7 +101,7 @@ const USER_COLUMNS: &str = "SELECT id, iota_id, username, display, status, prese #[derive(FromRow)] struct UserRow { id: i64, - iota_id: i64, + iota_id: Option, username: Vec, display: Option>, status: Option>, @@ -124,7 +124,7 @@ impl TryFrom for User { |value| String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error))); Ok(User { id: row.id.into(), - iota_id: row.iota_id.into(), + iota_id: row.iota_id.map(IotaId::from), username: decode(row.username)?, display: row.display.map(decode).transpose()?, status: row.status.map(decode).transpose()?, @@ -344,9 +344,9 @@ pub async fn change_presence_preference(id: UserId, value: String) -> Result<()> .await } -pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> { +pub async fn change_iota_id(id: UserId, value: Option) -> Result<()> { sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") - .bind(value.0) + .bind(value.map(|id| id.0)) .bind(id.0) .execute(&pool().await?) .await?; @@ -368,6 +368,42 @@ pub async fn delete_user(id: UserId) -> Result<()> { Ok(()) } +/// Delete the central identity while retaining a durable instruction for the +/// last hosting Iota. The pending row is intentionally independent of users: +/// it must outlive the account row. +pub async fn delete_user_with_pending_erasure(id: UserId) -> Result> { + let mut tx = pool().await?.begin().await?; + let row = sqlx::query("SELECT iota_id FROM users WHERE id = ? FOR UPDATE") + .bind(id.0) + .fetch_optional(&mut *tx) + .await? + .ok_or(OmegaError::NotFound)?; + let iota_id: Option = row.get("iota_id"); + if let Some(iota_id) = iota_id { + sqlx::query("INSERT IGNORE INTO pending_iota_user_erasure (user_id, iota_id) VALUES (?, ?)") + .bind(id.0) + .bind(iota_id) + .execute(&mut *tx) + .await?; + } + sqlx::query("DELETE FROM registration_leases WHERE user_id = ?").bind(id.0).execute(&mut *tx).await?; + sqlx::query("DELETE FROM users WHERE id = ?").bind(id.0).execute(&mut *tx).await?; + tx.commit().await?; + Ok(iota_id.map(IotaId::from)) +} + +pub async fn pending_erasures_for_iota(iota_id: IotaId) -> Result> { + let rows = sqlx::query("SELECT user_id FROM pending_iota_user_erasure WHERE iota_id = ?") + .bind(iota_id.0).fetch_all(&pool().await?).await?; + Ok(rows.into_iter().map(|row| UserId::from(row.get::("user_id"))).collect()) +} + +pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Result { + let result = sqlx::query("DELETE FROM pending_iota_user_erasure WHERE user_id = ? AND iota_id = ?") + .bind(user_id.0).bind(iota_id.0).execute(&pool().await?).await?; + Ok(result.rows_affected() == 1) +} + pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> { sqlx::query("UPDATE users SET public_key = ? WHERE id = ?") .bind(public_key.as_bytes()) @@ -438,7 +474,7 @@ pub async fn register_complete_user( .map_err(OmegaError::from)? { Some(existing) - if existing.iota_id == iota_id + if existing.iota_id == Some(iota_id) && existing.username == username && existing.public_key.as_bytes() == public_key.as_bytes() && existing.token == token => diff --git a/src/models/user.rs b/src/models/user.rs index 2843b6d..a9595d9 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -4,7 +4,7 @@ use mtp::crypto::PublicKeyBundle; #[derive(Clone, Debug, serde::Serialize)] pub struct User { pub id: UserId, - pub iota_id: IotaId, + pub iota_id: Option, pub username: String, pub display: Option, pub status: Option, diff --git a/src/server/api.rs b/src/server/api.rs index 498685a..0319607 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -41,7 +41,7 @@ fn user_response(user: crate::models::User) -> UserResponse { username: user.username, public_key: user.public_key.to_base64(), user_id: user.id.0, - iota_id: user.iota_id.0, + iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, sub_end: user.sub_end, display: user.display, @@ -83,7 +83,15 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { omikron_id } else { let user = get_by_user_id(UserId::from(id)).await?; - get_iota_primary_omikron_connection(user.iota_id.0).ok_or(OmegaError::NotFound)? + match user.iota_id { + Some(iota_id) => get_iota_primary_omikron_connection(iota_id.0), + None => get_random_omikron() + .await + .map_err(|_| OmegaError::NotFound)? + .get_omikron_id() + .await, + } + .ok_or(OmegaError::NotFound)? }; // Database rows describe registered Omikrons. The public discovery @@ -150,7 +158,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { username: user.username, public_key: user.public_key.to_base64(), user_id: user.id.0, - iota_id: user.iota_id.0, + iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, sub_end: user.sub_end, }), diff --git a/src/state.rs b/src/state.rs index dc91021..075d268 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,18 +1,47 @@ use crate::sql::user_online_tracker::PresenceTracker; use std::sync::Arc; +use dashmap::DashMap; +use std::time::{Duration, Instant}; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum AccountChallengeOperation { Attach, Delete } + +#[derive(Clone, Debug)] +pub struct AccountChallenge { + pub operation: AccountChallengeOperation, + pub user_id: i64, + pub requester_iota_id: i64, + pub nonce: u64, + pub created_at: Instant, +} pub struct OmegaState { pub presence: Arc, + challenges: DashMap<(AccountChallengeOperation, i64, i64), AccountChallenge>, } impl Default for OmegaState { fn default() -> Self { Self { presence: Arc::new(PresenceTracker::default()), + challenges: DashMap::new(), } } } +impl OmegaState { + pub fn issue_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64) -> u64 { + let nonce = rand::random::(); + self.challenges.insert((operation, user_id, requester_iota_id), AccountChallenge { operation, user_id, requester_iota_id, nonce, created_at: Instant::now() }); + nonce + } + + pub fn consume_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64, nonce: u64) -> bool { + self.challenges.remove(&(operation, user_id, requester_iota_id)).is_some_and(|(_, value)| + value.nonce == nonce && value.created_at.elapsed() <= Duration::from_secs(120)) + } +} + impl OmegaState { pub fn new() -> Arc { Arc::new(Self::default()) diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs index 1927b14..360076d 100644 --- a/src/transport/handlers/account.rs +++ b/src/transport/handlers/account.rs @@ -2,8 +2,9 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, + state::AccountChallengeOperation, }; -use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; +use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, crypto::{verify_ed25519, verify_ml_dsa}}; use std::sync::Arc; async fn delete( @@ -23,12 +24,8 @@ pub async fn user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - delete( - connection, - value.clone(), - user_repo::delete_user(UserId::from(value.get_sender() as i64)), - ) - .await + let user_id = UserId::from(value.get_sender() as i64); + complete_delete(connection, value, user_id).await } pub async fn iota( connection: Arc, @@ -41,3 +38,157 @@ pub async fn iota( ) .await } + +pub async fn release_from_iota( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; + let requester = IotaId::from(value.get_sender() as i64); + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; + if user.iota_id != Some(requester) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await; + } + let previous_iota = user.iota_id; + match user_repo::change_iota_id(user.id, None).await { + Ok(()) => { + if let Some(iota) = previous_iota { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } + connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await + }, + Err(error) => connection + .send(&CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))) + .await, + } +} + +fn lifecycle_payload(domain: &[u8], user_id: i64, iota_id: i64, nonce: u64) -> Vec { + let mut payload = Vec::with_capacity(domain.len() + 32); + payload.extend_from_slice(domain); + payload.extend_from_slice(&user_id.to_be_bytes()); + payload.extend_from_slice(&iota_id.to_be_bytes()); + payload.extend_from_slice(&nonce.to_be_bytes()); + payload +} + +pub async fn attach_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { + return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; + }; + if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { + return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; + } + let requester = value.get_sender() as i64; + let nonce = connection.state().issue_challenge(AccountChallengeOperation::Attach, user_id, requester); + connection.send(&CommunicationValue::new(CommunicationType::AttachUserChallenge).with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await +} + +pub async fn attach_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; + let requester = value.get_sender() as i64; + let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + let signature = value.get_data(DataType::Signature).as_bytes(); + let pq_signature = value.get_data(DataType::PqSignature).as_bytes(); + let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + if !connection.state().consume_challenge(AccountChallengeOperation::Attach, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce); + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + let previous_iota = user.iota_id; + match user_repo::change_iota_id(user.id, Some(IotaId::from(requester))).await { + Ok(()) => { + if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } + crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; + connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await + }, + Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + } +} + +async fn complete_delete(connection: Arc, value: CommunicationValue, user_id: UserId) -> OmikronResult<()> { + match user_repo::delete_user_with_pending_erasure(user_id).await { + Ok(iota_id) => { + let cleanup_pending = iota_id.is_some(); + if let Some(iota_id) = iota_id { + crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id.0).await; + crate::transport::omikron_manager::deliver_pending_erasures(iota_id.0).await; + } + connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()) + .add_typed_default(DataType::CleanupPending, DataValue::Bool(cleanup_pending))).await + } + Err(crate::error::OmegaError::NotFound) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await, + Err(error) => connection.send(&CommunicationValue::new(CommunicationType::ErrorInternal).with_id(value.get_id()).add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))).await, + } +} + +pub async fn delete_credential_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { + return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; + }; + if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; } + let requester = value.get_sender() as i64; + let nonce = connection.state().issue_challenge(AccountChallengeOperation::Delete, user_id, requester); + connection.send(&CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge).with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await +} + +pub async fn delete_credential_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; + let requester = value.get_sender() as i64; + let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + let (Some(signature), Some(pq_signature)) = (value.get_data(DataType::Signature).as_bytes(), value.get_data(DataType::PqSignature).as_bytes()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + if !connection.state().consume_challenge(AccountChallengeOperation::Delete, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce); + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + complete_delete(connection, value, user.id).await +} + +pub async fn erase_hosted_user_data_ack(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { + let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; + let iota_id = IotaId::from(value.get_sender() as i64); + match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await { + Ok(true) => connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await, + Ok(false) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await, + Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + } +} + +/// New lifecycle operation names are intentionally fail-closed until their +/// proof and durable-erasure handlers are enabled. This explicit dispatch +/// prevents either a bare Iota request or the legacy DeleteUser path from +/// acquiring account-deletion authority during a staged rollout. +pub async fn lifecycle_unavailable( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorNotAuthenticated) + .with_id(value.get_id()) + .add_typed_default( + DataType::ErrorType, + DataValue::Str("user lifecycle proof handler is not enabled".into()), + ), + ) + .await +} diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index 0c31904..de8a275 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -53,7 +53,7 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa user.id.0, state .presence - .resolve_public_state(user.id.0, user.iota_id.0), + .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()), ) }) .collect() @@ -69,7 +69,7 @@ fn changed_states( .filter_map(|user| { let after = state .presence - .resolve_public_state(user.id.0, user.iota_id.0); + .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()); (before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after)) }) .collect::>(); @@ -275,7 +275,7 @@ pub async fn user_connected( Ok(preferences) => preferences, Err(error) => return Err(error.into()), }; - if user.iota_id.0 != iota_id || !state.presence.has_iota_route(iota_id) { + if user.iota_id.map(|id| id.0) != Some(iota_id) || !state.presence.has_iota_route(iota_id) { return connection .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) .await; @@ -497,6 +497,7 @@ pub async fn iota_connected( .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); connection.clone().send(&response).await?; + crate::transport::omikron_manager::deliver_pending_erasures(iota_id).await; publish_changed_states(&state, &before, &users).await; connection .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index 690a887..d147f27 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -110,7 +110,7 @@ pub async fn get( }; let status = state .presence - .resolve_public_state(user_id, user.iota_id.0) + .resolve_public_state(user_id, user.iota_id.map(|id| id.0).unwrap_or_default()) .to_string(); let mut map = Vec::new(); if let Some(kind) = DataType::UserId.try_to_id(&tm) { diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index 7e79bee..e331125 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -43,7 +43,7 @@ pub async fn get_user( .await; }; let id = user.id.0; - let iota_id = user.iota_id.0; + let iota_id = user.iota_id.map(|id| id.0); let username = user.username.clone(); let display = user .display @@ -57,7 +57,6 @@ pub async fn get_user( DataValue::Str(user.public_key.to_base64()), ) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) - .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) .add_typed_default(DataType::Display, DataValue::Str(display)) .add_typed_default( DataType::SubLevel, @@ -92,7 +91,7 @@ pub async fn get_user( } state.presence.resolve_private_state(id) } else { - state.presence.resolve_public_state(id, iota_id) + iota_id.map(|iota_id| state.presence.resolve_public_state(id, iota_id)).unwrap_or(crate::sql::connection_status::UserStatus::user_offline) }; response = response .add_typed_default( @@ -101,8 +100,11 @@ pub async fn get_user( ) .add_typed_default( DataType::OmikronConnections, - connections(&connection, iota_id), + iota_id.map(|iota_id| connections(&connection, iota_id)).unwrap_or_else(|| DataValue::Array(Vec::new())), ); + if let Some(iota_id) = iota_id { + response = response.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); + } if let Some(route) = route { response = response.add_typed_default( DataType::OmikronId, @@ -123,26 +125,25 @@ pub async fn get_iota( .map(|iota| (iota.id.0, iota.public_key, None, None)) } else if let Some(id) = value.get_data(DataType::UserId).as_number() { if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await { - iota_repo::get_iota_by_id(user.iota_id) - .await - .ok() - .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)) + match user.iota_id { + Some(iota_id) => iota_repo::get_iota_by_id(iota_id) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)), + None => None, + } } else { None } } else if let Some(name) = value.get_data(DataType::Username).as_str() { if let Ok(user) = user_repo::get_by_username(name).await { - iota_repo::get_iota_by_id(user.iota_id) - .await - .ok() - .map(|iota| { - ( - iota.id.0, - iota.public_key, - Some(user.id.0), - Some(name.to_owned()), - ) - }) + match user.iota_id { + Some(iota_id) => iota_repo::get_iota_by_id(iota_id) + .await + .ok() + .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), Some(name.to_owned()))), + None => None, + } } else { None } @@ -269,7 +270,7 @@ pub async fn change_iota( .await; } let result = - match user_repo::change_iota_id(user_id, IotaId::from(value.get_sender() as i64)).await { + match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))).await { Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, Err(error) => Err(error), }; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 5001ff3..7ad0136 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -87,6 +87,7 @@ impl OmikronConnection { &self.peer_capabilities } + pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, @@ -215,6 +216,24 @@ impl OmikronConnection { Some(CommunicationType::DeleteUser) => { crate::transport::handlers::account::user(self, value).await } + Some(CommunicationType::AttachUserBegin) => { + crate::transport::handlers::account::attach_begin(self, value).await + } + Some(CommunicationType::AttachUserComplete) => { + crate::transport::handlers::account::attach_complete(self, value).await + } + Some(CommunicationType::DeleteUserCredentialBegin) => { + crate::transport::handlers::account::delete_credential_begin(self, value).await + } + Some(CommunicationType::DeleteUserCredentialComplete) => { + crate::transport::handlers::account::delete_credential_complete(self, value).await + } + Some(CommunicationType::EraseHostedUserDataAck) => { + crate::transport::handlers::account::erase_hosted_user_data_ack(self, value).await + } + Some(CommunicationType::ReleaseUserFromIota) => { + crate::transport::handlers::account::release_from_iota(self, value).await + } Some(CommunicationType::DeleteIota) => { crate::transport::handlers::account::iota(self, value).await } diff --git a/src/transport/omikron_manager.rs b/src/transport/omikron_manager.rs index 254ced8..9020556 100644 --- a/src/transport/omikron_manager.rs +++ b/src/transport/omikron_manager.rs @@ -3,7 +3,7 @@ use crate::state::OmegaState; use crate::transport::connection::OmikronConnection; use crate::transport::omikron_connection::OmikronResult; use dashmap::DashMap; -use mtp::codec::CommunicationValue; +use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use once_cell::sync::Lazy; use rand::prelude::IteratorRandom; use std::sync::Arc; @@ -65,7 +65,8 @@ pub async fn get_all_connections() for user in users { for route in state.presence.routes_for_user(user.id.0) { if let Some(iotas) = result.get_mut(&route.omikron_id) { - if let Some(users) = iotas.get_mut(&user.iota_id.0) { + if let Some(iota_id) = user.iota_id + && let Some(users) = iotas.get_mut(&iota_id.0) { users.push(user.id.0); } } @@ -113,3 +114,28 @@ pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) { } } } + +/// Publish the authoritative membership list after an attach, migration, or +/// release. Omikron replaces its full local index from this snapshot. +pub async fn publish_iota_user_snapshot(iota_id: i64) { + let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) else { return; }; + let Some(connection) = get_connected_omikron(omikron_id) else { return; }; + let Ok(users) = user_repo::get_users_by_iota_id(crate::models::IotaId::from(iota_id)).await else { return; }; + let user_ids = users.into_iter().map(|user| DataValue::SignedNumber(user.id.0.into())).collect(); + let snapshot = CommunicationValue::new(CommunicationType::IotaUserData) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())) + .add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); + let _ = connection.send(&snapshot).await; +} + +pub async fn deliver_pending_erasures(iota_id: i64) { + let Ok(users) = user_repo::pending_erasures_for_iota(crate::models::IotaId::from(iota_id)).await else { return; }; + let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) else { return; }; + let Some(connection) = get_connected_omikron(omikron_id) else { return; }; + for user_id in users { + let request = CommunicationValue::new(CommunicationType::EraseHostedUserData) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.0.into())) + .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); + let _ = connection.clone().send(&request).await; + } +} From 7709fe599a0cdfdbf6fc6b97244c49e39e9dc8f2 Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:37:14 +0200 Subject: [PATCH 213/220] [WIP] 0.3.0 mtp update --- Cargo.lock | 335 ++++++++++++++---------- Cargo.toml | 3 +- src/db/user_repo.rs | 39 ++- src/server/api.rs | 12 +- src/sql/user_online_tracker.rs | 32 ++- src/state.rs | 40 ++- src/transport/connection.rs | 49 +++- src/transport/handlers/account.rs | 316 ++++++++++++++++++---- src/transport/handlers/links.rs | 8 +- src/transport/handlers/notifications.rs | 4 +- src/transport/handlers/presence.rs | 21 +- src/transport/handlers/register.rs | 4 +- src/transport/handlers/states.rs | 6 +- src/transport/handlers/user_data.rs | 37 ++- src/transport/mod.rs | 1 + src/transport/omikron_connection.rs | 97 ++++++- src/transport/omikron_manager.rs | 35 ++- src/transport/relay_router.rs | 243 +++++++++++++++++ src/util/logger.rs | 5 +- 19 files changed, 1009 insertions(+), 278 deletions(-) create mode 100644 src/transport/relay_router.rs diff --git a/Cargo.lock b/Cargo.lock index 1e03155..cd6c90d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + [[package]] name = "asn1-rs" version = "0.7.2" @@ -56,7 +68,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 2.0.20", "time", ] @@ -85,9 +97,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", @@ -175,6 +187,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -223,9 +244,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.1" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -740,9 +761,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flate2" @@ -800,9 +821,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -815,9 +836,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -825,15 +846,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -853,38 +874,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -936,9 +957,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" dependencies = [ "atomic-waker", "bytes", @@ -1126,9 +1147,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -1223,9 +1244,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1237,9 +1258,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1250,9 +1271,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1264,16 +1285,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1284,15 +1306,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" dependencies = [ "displaydoc", "icu_locale_core", @@ -1376,7 +1398,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -1425,9 +1447,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -1482,14 +1504,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.9.1", + "redox_syscall 0.9.2", ] [[package]] @@ -1504,9 +1526,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lock_api" @@ -1642,8 +1664,8 @@ dependencies = [ [[package]] name = "mtp" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-client", "mtp-codec", @@ -1658,8 +1680,8 @@ dependencies = [ [[package]] name = "mtp-client" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-codec", "mtp-common", @@ -1671,8 +1693,8 @@ dependencies = [ [[package]] name = "mtp-codec" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "base64 0.23.1", "byteorder", @@ -1680,25 +1702,27 @@ dependencies = [ "mtp-crypto", "mtp-type-map", "rand 0.10.2", + "thiserror 2.0.20", ] [[package]] name = "mtp-common" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "quinn", "rustls", - "thiserror", + "thiserror 2.0.20", "wtransport", ] [[package]] name = "mtp-crypto" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ - "base64 0.23.1", + "argon2", + "base64 0.22.1", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", @@ -1706,36 +1730,37 @@ dependencies = [ "ml-dsa", "mlkem-tls", "rand 0.10.2", - "rand_core 0.10.1", + "rand_core 0.6.4", "rustls", "serde", "sha2 0.11.0", - "thiserror", + "thiserror 1.0.69", "tokio", "zeroize", ] [[package]] name = "mtp-files" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-crypto", "rand 0.10.2", - "thiserror", + "thiserror 2.0.20", "zeroize", ] [[package]] name = "mtp-host" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", "rand 0.10.2", + "thiserror 2.0.20", "tokio", "tracing", "wtransport", @@ -1743,13 +1768,14 @@ dependencies = [ [[package]] name = "mtp-transport" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "async-trait", "mtp-codec", "mtp-common", "mtp-crypto", + "rand 0.10.2", "rcgen", "rustls", "rustls-native-certs", @@ -1757,12 +1783,13 @@ dependencies = [ "tokio", "tracing", "wtransport", + "zeroize", ] [[package]] name = "mtp-type-map" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "serde", "serde_yaml", @@ -1770,8 +1797,8 @@ dependencies = [ [[package]] name = "mtp-webserver" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "async-trait", "bytes", @@ -1788,9 +1815,8 @@ dependencies = [ "mtp-host", "mtp-transport", "quinn", - "rand 0.10.2", "rustls", - "thiserror", + "thiserror 2.0.20", "tokio", "tokio-rustls", "tokio-stream", @@ -1841,9 +1867,9 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -1903,7 +1929,7 @@ dependencies = [ "sqlx", "strum", "strum_macros", - "thiserror", + "thiserror 2.0.20", "tokio", "uuid", "zip", @@ -1956,6 +1982,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pbkdf2" version = "0.13.0" @@ -2039,9 +2076,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "plain" @@ -2062,15 +2099,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -2120,7 +2157,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.20", "tokio", "tracing", "web-time", @@ -2128,9 +2165,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.16" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" dependencies = [ "aws-lc-rs", "bytes", @@ -2145,7 +2182,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror", + "thiserror 2.0.20", "tinyvec", "tracing", "web-time", @@ -2238,9 +2275,9 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.14.8" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" +checksum = "091e7a8e7d86e6feb87a27ce8e2cba29d49eff9507afeebefab7eeb2ca667fb4" dependencies = [ "aws-lc-rs", "pem", @@ -2262,9 +2299,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" +checksum = "f1c93da5bb2c5d4e6c0ef7abeead62c89169a0a4882bfb83ac892f2423aea2fe" dependencies = [ "bitflags", ] @@ -2432,9 +2469,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" dependencies = [ "aws-lc-rs", "ring", @@ -2804,7 +2841,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror", + "thiserror 2.0.20", "tokio", "tokio-stream", "tracing", @@ -2886,7 +2923,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.20", "tracing", "whoami", ] @@ -2923,7 +2960,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.20", "tracing", "whoami", ] @@ -2947,7 +2984,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.20", "tracing", "url", ] @@ -3058,18 +3095,38 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -3109,9 +3166,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -3365,9 +3422,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.24.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -3419,9 +3476,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -3432,9 +3489,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -3442,9 +3499,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3452,9 +3509,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -3465,18 +3522,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -3727,15 +3784,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "wtransport" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4aacf790813ee1956751491800537f4e04af7557b7b370501ccbfbc85963e4" +checksum = "b4273ce3157a3262a68665f8d3f20a0ac0c5b8a69ffd67f05ae986832ebec036" dependencies = [ "bytes", "pem", @@ -3746,7 +3803,7 @@ dependencies = [ "rustls-pki-types", "sha2 0.11.0", "socket2", - "thiserror", + "thiserror 2.0.20", "time", "tokio", "tracing", @@ -3757,13 +3814,13 @@ dependencies = [ [[package]] name = "wtransport-proto" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" +checksum = "aad9059572c7dbd6901ccef37f3b7321678cd708dcf58a64b1921dbeab7bfede" dependencies = [ "httlib-huffman", "octets", - "thiserror", + "thiserror 2.0.20", "url", ] @@ -3794,7 +3851,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror", + "thiserror 2.0.20", "time", ] @@ -3894,9 +3951,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -3905,9 +3962,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" dependencies = [ "yoke", "zerofrom", @@ -3916,13 +3973,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3b4f6a3..28e732a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,10 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ +mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ "crypto", "files", + "raw", "web-server", ] } diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index df99a18..79b9034 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -380,27 +380,44 @@ pub async fn delete_user_with_pending_erasure(id: UserId) -> Result = row.get("iota_id"); if let Some(iota_id) = iota_id { - sqlx::query("INSERT IGNORE INTO pending_iota_user_erasure (user_id, iota_id) VALUES (?, ?)") - .bind(id.0) - .bind(iota_id) - .execute(&mut *tx) - .await?; + sqlx::query( + "INSERT IGNORE INTO pending_iota_user_erasure (user_id, iota_id) VALUES (?, ?)", + ) + .bind(id.0) + .bind(iota_id) + .execute(&mut *tx) + .await?; } - sqlx::query("DELETE FROM registration_leases WHERE user_id = ?").bind(id.0).execute(&mut *tx).await?; - sqlx::query("DELETE FROM users WHERE id = ?").bind(id.0).execute(&mut *tx).await?; + sqlx::query("DELETE FROM registration_leases WHERE user_id = ?") + .bind(id.0) + .execute(&mut *tx) + .await?; + sqlx::query("DELETE FROM users WHERE id = ?") + .bind(id.0) + .execute(&mut *tx) + .await?; tx.commit().await?; Ok(iota_id.map(IotaId::from)) } pub async fn pending_erasures_for_iota(iota_id: IotaId) -> Result> { let rows = sqlx::query("SELECT user_id FROM pending_iota_user_erasure WHERE iota_id = ?") - .bind(iota_id.0).fetch_all(&pool().await?).await?; - Ok(rows.into_iter().map(|row| UserId::from(row.get::("user_id"))).collect()) + .bind(iota_id.0) + .fetch_all(&pool().await?) + .await?; + Ok(rows + .into_iter() + .map(|row| UserId::from(row.get::("user_id"))) + .collect()) } pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Result { - let result = sqlx::query("DELETE FROM pending_iota_user_erasure WHERE user_id = ? AND iota_id = ?") - .bind(user_id.0).bind(iota_id.0).execute(&pool().await?).await?; + let result = + sqlx::query("DELETE FROM pending_iota_user_erasure WHERE user_id = ? AND iota_id = ?") + .bind(user_id.0) + .bind(iota_id.0) + .execute(&pool().await?) + .await?; Ok(result.rows_affected() == 1) } diff --git a/src/server/api.rs b/src/server/api.rs index 0319607..23debe4 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -85,11 +85,13 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { let user = get_by_user_id(UserId::from(id)).await?; match user.iota_id { Some(iota_id) => get_iota_primary_omikron_connection(iota_id.0), - None => get_random_omikron() - .await - .map_err(|_| OmegaError::NotFound)? - .get_omikron_id() - .await, + None => { + get_random_omikron() + .await + .map_err(|_| OmegaError::NotFound)? + .get_omikron_id() + .await + } } .ok_or(OmegaError::NotFound)? }; diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 8017c3d..be3e87c 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1044,20 +1044,24 @@ mod tests { assert_eq!(tracker.subscribers(22).len(), 1); tracker.remove_session(7, 3, 42); assert!(tracker.check_index_consistency().is_ok()); - assert!(tracker - .routes - .read() - .unwrap() - .indices - .sessions_by_user - .is_empty()); - assert!(tracker - .routes - .read() - .unwrap() - .indices - .targets_by_subscriber - .is_empty()); + assert!( + tracker + .routes + .read() + .unwrap() + .indices + .sessions_by_user + .is_empty() + ); + assert!( + tracker + .routes + .read() + .unwrap() + .indices + .targets_by_subscriber + .is_empty() + ); } #[test] diff --git a/src/state.rs b/src/state.rs index 075d268..02c899b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,10 +1,13 @@ use crate::sql::user_online_tracker::PresenceTracker; -use std::sync::Arc; use dashmap::DashMap; +use std::sync::Arc; use std::time::{Duration, Instant}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum AccountChallengeOperation { Attach, Delete } +pub enum AccountChallengeOperation { + Attach, + Delete, +} #[derive(Clone, Debug)] pub struct AccountChallenge { @@ -30,15 +33,38 @@ impl Default for OmegaState { } impl OmegaState { - pub fn issue_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64) -> u64 { + pub fn issue_challenge( + &self, + operation: AccountChallengeOperation, + user_id: i64, + requester_iota_id: i64, + ) -> u64 { let nonce = rand::random::(); - self.challenges.insert((operation, user_id, requester_iota_id), AccountChallenge { operation, user_id, requester_iota_id, nonce, created_at: Instant::now() }); + self.challenges.insert( + (operation, user_id, requester_iota_id), + AccountChallenge { + operation, + user_id, + requester_iota_id, + nonce, + created_at: Instant::now(), + }, + ); nonce } - pub fn consume_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64, nonce: u64) -> bool { - self.challenges.remove(&(operation, user_id, requester_iota_id)).is_some_and(|(_, value)| - value.nonce == nonce && value.created_at.elapsed() <= Duration::from_secs(120)) + pub fn consume_challenge( + &self, + operation: AccountChallengeOperation, + user_id: i64, + requester_iota_id: i64, + nonce: u64, + ) -> bool { + self.challenges + .remove(&(operation, user_id, requester_iota_id)) + .is_some_and(|(_, value)| { + value.nonce == nonce && value.created_at.elapsed() <= Duration::from_secs(120) + }) } } diff --git a/src/transport/connection.rs b/src/transport/connection.rs index eff0bb1..6cada3e 100644 --- a/src/transport/connection.rs +++ b/src/transport/connection.rs @@ -1 +1,48 @@ -pub(crate) use super::omikron_connection::OmikronConnection; +pub(crate) use super::omikron_connection::{OmikronConnection, OmikronResult}; + +use mtp::codec::{CommunicationValue, DataValue}; + +pub(crate) trait MtpValueCompat { + fn get_id(&self) -> u32; + fn get_sender(&self) -> u64; + fn get_receiver(&self) -> u64; +} + +impl MtpValueCompat for CommunicationValue { + fn get_id(&self) -> u32 { + self.id().unwrap_or_default() + } + + fn get_sender(&self) -> u64 { + self.sender().unwrap_or_default() + } + + fn get_receiver(&self) -> u64 { + self.receiver().unwrap_or_default() + } +} + +pub(crate) trait OptionalDataValueCompat { + fn as_number(&self) -> Option; + fn as_signed_number(&self) -> Option; + fn as_str(&self) -> Option<&str>; + fn as_bytes(&self) -> Option>; +} + +impl OptionalDataValueCompat for Option<&DataValue> { + fn as_number(&self) -> Option { + self.and_then(|value| value.as_number()) + } + + fn as_signed_number(&self) -> Option { + self.and_then(|value| value.as_signed_number()) + } + + fn as_str(&self) -> Option<&str> { + self.and_then(|value| value.as_str()) + } + + fn as_bytes(&self) -> Option> { + self.and_then(|value| value.as_bytes()) + } +} diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs index 360076d..0374f78 100644 --- a/src/transport/handlers/account.rs +++ b/src/transport/handlers/account.rs @@ -1,10 +1,15 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, state::AccountChallengeOperation, }; -use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, crypto::{verify_ed25519, verify_ml_dsa}}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::{verify_ed25519, verify_ml_dsa}, +}; use std::sync::Arc; async fn delete( @@ -67,14 +72,22 @@ pub async fn release_from_iota( let previous_iota = user.iota_id; match user_repo::change_iota_id(user.id, None).await { Ok(()) => { - if let Some(iota) = previous_iota { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } - connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await - }, - Err(error) => connection - .send(&CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))) - .await, + if let Some(iota) = previous_iota { + crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; + } + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await + } } } @@ -87,43 +100,121 @@ fn lifecycle_payload(domain: &[u8], user_id: i64, iota_id: i64, nonce: u64) -> V payload } -pub async fn attach_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { - return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; +pub async fn attach_begin( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; }; - if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { - return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; + if user_repo::get_by_user_id(UserId::from(user_id)) + .await + .is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; } let requester = value.get_sender() as i64; - let nonce = connection.state().issue_challenge(AccountChallengeOperation::Attach, user_id, requester); - connection.send(&CommunicationValue::new(CommunicationType::AttachUserChallenge).with_id(value.get_id()) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) - .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await + let nonce = + connection + .state() + .issue_challenge(AccountChallengeOperation::Attach, user_id, requester); + connection + .send( + &CommunicationValue::new(CommunicationType::AttachUserChallenge) + .with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), + ) + .await } -pub async fn attach_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; +pub async fn attach_complete( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; let requester = value.get_sender() as i64; - let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + let Some(nonce) = value + .get_data(DataType::ServerNonce) + .as_signed_number() + .and_then(|v| u64::try_from(v).ok()) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; let signature = value.get_data(DataType::Signature).as_bytes(); let pq_signature = value.get_data(DataType::PqSignature).as_bytes(); - let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; - if !connection.state().consume_challenge(AccountChallengeOperation::Attach, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } - let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; + if !connection.state().consume_challenge( + AccountChallengeOperation::Attach, + user_id, + requester, + nonce, + ) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce); - if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() + || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await; + } let previous_iota = user.iota_id; match user_repo::change_iota_id(user.id, Some(IotaId::from(requester))).await { Ok(()) => { - if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } + if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { + crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; + } crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; - connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await - }, - Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Err(_) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .await + } } } -async fn complete_delete(connection: Arc, value: CommunicationValue, user_id: UserId) -> OmikronResult<()> { +async fn complete_delete( + connection: Arc, + value: CommunicationValue, + user_id: UserId, +) -> OmikronResult<()> { match user_repo::delete_user_with_pending_erasure(user_id).await { Ok(iota_id) => { let cleanup_pending = iota_id.is_some(); @@ -131,45 +222,160 @@ async fn complete_delete(connection: Arc, value: Communicatio crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id.0).await; crate::transport::omikron_manager::deliver_pending_erasures(iota_id.0).await; } - connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()) - .add_typed_default(DataType::CleanupPending, DataValue::Bool(cleanup_pending))).await + connection + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.get_id()) + .add_typed_default( + DataType::CleanupPending, + DataValue::Bool(cleanup_pending), + ), + ) + .await + } + Err(crate::error::OmegaError::NotFound) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await } - Err(crate::error::OmegaError::NotFound) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await, - Err(error) => connection.send(&CommunicationValue::new(CommunicationType::ErrorInternal).with_id(value.get_id()).add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))).await, } } -pub async fn delete_credential_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { - return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; +pub async fn delete_credential_begin( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; }; - if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; } + if user_repo::get_by_user_id(UserId::from(user_id)) + .await + .is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + } let requester = value.get_sender() as i64; - let nonce = connection.state().issue_challenge(AccountChallengeOperation::Delete, user_id, requester); - connection.send(&CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge).with_id(value.get_id()) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) - .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await + let nonce = + connection + .state() + .issue_challenge(AccountChallengeOperation::Delete, user_id, requester); + connection + .send( + &CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge) + .with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), + ) + .await } -pub async fn delete_credential_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; +pub async fn delete_credential_complete( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; let requester = value.get_sender() as i64; - let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; - let (Some(signature), Some(pq_signature)) = (value.get_data(DataType::Signature).as_bytes(), value.get_data(DataType::PqSignature).as_bytes()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; - if !connection.state().consume_challenge(AccountChallengeOperation::Delete, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } - let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let Some(nonce) = value + .get_data(DataType::ServerNonce) + .as_signed_number() + .and_then(|v| u64::try_from(v).ok()) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; + let (Some(signature), Some(pq_signature)) = ( + value.get_data(DataType::Signature).as_bytes(), + value.get_data(DataType::PqSignature).as_bytes(), + ) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; + if !connection.state().consume_challenge( + AccountChallengeOperation::Delete, + user_id, + requester, + nonce, + ) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce); - if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() + || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await; + } complete_delete(connection, value, user.id).await } -pub async fn erase_hosted_user_data_ack(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; +pub async fn erase_hosted_user_data_ack( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; let iota_id = IotaId::from(value.get_sender() as i64); match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await { - Ok(true) => connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await, - Ok(false) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await, - Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + Ok(true) => { + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Ok(false) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await + } + Err(_) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .await + } } } diff --git a/src/transport/handlers/links.rs b/src/transport/handlers/links.rs index a43cb99..ad45c05 100644 --- a/src/transport/handlers/links.rs +++ b/src/transport/handlers/links.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::server::short_link::add_short_link; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use std::sync::Arc; @@ -7,8 +9,8 @@ pub async fn shorten( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let link = value - .get_data(DataType::Link) + let link_data = value.get_data(DataType::Link); + let link = link_data .as_str() .ok_or(crate::error::OmegaError::InvalidResponse)?; let short = add_short_link(link) diff --git a/src/transport/handlers/notifications.rs b/src/transport/handlers/notifications.rs index 5f23474..9405bf0 100644 --- a/src/transport/handlers/notifications.rs +++ b/src/transport/handlers/notifications.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{db::notification_repo, log, models::UserId}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::type_map::TypeMap; diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index de8a275..5c631bf 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::user_repo, log_in, models::IotaId, sql::connection_status::UserStatus, state::OmegaState, }; @@ -19,7 +21,7 @@ fn parse_subscription(value: &CommunicationValue) -> Result<(i64, i64, Vec) .and_then(|id| i64::try_from(id).ok()) .filter(|id| *id > 0) .ok_or("session_id")?; - let DataValue::Array(values) = value.get_data(DataType::UserIds) else { + let Some(DataValue::Array(values)) = value.get_data(DataType::UserIds) else { return Err("user_ids"); }; @@ -51,9 +53,10 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa .map(|user| { ( user.id.0, - state - .presence - .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()), + state.presence.resolve_public_state( + user.id.0, + user.iota_id.map(|id| id.0).unwrap_or_default(), + ), ) }) .collect() @@ -349,7 +352,7 @@ pub async fn set_user_state( .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) .await; }; - if let Some(requested_user) = value.get_data_opt(DataType::UserId) { + if let Some(requested_user) = value.get_data(DataType::UserId) { let Some(requested_user_id) = requested_user .as_number() .and_then(|id| i64::try_from(id).ok()) @@ -538,12 +541,12 @@ pub async fn sync_status( omikron_id: i64, ) -> OmikronResult<()> { let request_id = value.get_id(); - let DataValue::Array(iota_values) = value.get_data(DataType::IotaIds) else { + let Some(DataValue::Array(iota_values)) = value.get_data(DataType::IotaIds) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) .await; }; - let DataValue::Array(session_values) = value.get_data(DataType::UserStates) else { + let Some(DataValue::Array(session_values)) = value.get_data(DataType::UserStates) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) .await; @@ -572,7 +575,7 @@ pub async fn sync_status( } if !connection.peer_capabilities().session_snapshot_v1 { - let DataValue::Array(user_values) = value.get_data(DataType::UserIds) else { + let Some(DataValue::Array(user_values)) = value.get_data(DataType::UserIds) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) .await; diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs index 3502269..8799893 100644 --- a/src/transport/handlers/register.rs +++ b/src/transport/handlers/register.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index d147f27..e1fc12d 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::db::user_repo; use mtp::{ codec::{CommunicationType, CommunicationValue, DataType, DataValue}, @@ -29,7 +31,7 @@ pub async fn get( ) -> OmikronResult<()> { let state = connection.state(); let legacy_peer = !connection.peer_capabilities().client_state_push_v1; - let DataValue::Array(ids) = value.get_data(DataType::UserIds) else { + let Some(DataValue::Array(ids)) = value.get_data(DataType::UserIds) else { return send_error( connection, value.get_id(), diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index e331125..1c248fa 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, @@ -91,7 +93,9 @@ pub async fn get_user( } state.presence.resolve_private_state(id) } else { - iota_id.map(|iota_id| state.presence.resolve_public_state(id, iota_id)).unwrap_or(crate::sql::connection_status::UserStatus::user_offline) + iota_id + .map(|iota_id| state.presence.resolve_public_state(id, iota_id)) + .unwrap_or(crate::sql::connection_status::UserStatus::user_offline) }; response = response .add_typed_default( @@ -100,10 +104,13 @@ pub async fn get_user( ) .add_typed_default( DataType::OmikronConnections, - iota_id.map(|iota_id| connections(&connection, iota_id)).unwrap_or_else(|| DataValue::Array(Vec::new())), + iota_id + .map(|iota_id| connections(&connection, iota_id)) + .unwrap_or_else(|| DataValue::Array(Vec::new())), ); if let Some(iota_id) = iota_id { - response = response.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); + response = + response.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); } if let Some(route) = route { response = response.add_typed_default( @@ -138,10 +145,14 @@ pub async fn get_iota( } else if let Some(name) = value.get_data(DataType::Username).as_str() { if let Ok(user) = user_repo::get_by_username(name).await { match user.iota_id { - Some(iota_id) => iota_repo::get_iota_by_id(iota_id) - .await - .ok() - .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), Some(name.to_owned()))), + Some(iota_id) => iota_repo::get_iota_by_id(iota_id).await.ok().map(|iota| { + ( + iota.id.0, + iota.public_key, + Some(user.id.0), + Some(name.to_owned()), + ) + }), None => None, } } else { @@ -245,12 +256,14 @@ pub async fn change_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let Some(reset) = value.get_data(DataType::ResetToken).as_str() else { + let reset_data = value.get_data(DataType::ResetToken); + let Some(reset) = reset_data.as_str() else { return connection .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; }; - let Some(new_token) = value.get_data(DataType::NewToken).as_str() else { + let new_token_data = value.get_data(DataType::NewToken); + let Some(new_token) = new_token_data.as_str() else { return connection .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; @@ -270,7 +283,9 @@ pub async fn change_iota( .await; } let result = - match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))).await { + match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))) + .await + { Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, Err(error) => Err(error), }; diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 52c5fe8..3107217 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -3,3 +3,4 @@ pub mod connection; pub mod handlers; pub mod omikron_connection; pub mod omikron_manager; +pub mod relay_router; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 7ad0136..82b9ece 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -10,7 +10,7 @@ use dashmap::DashMap; use mtp::{ codec::{CommunicationType, CommunicationValue}, crypto::PublicKeyBundle, - host::{AuthenticationPolicy, HostConfig, Policy, SendMode}, + host::{AuthState, AuthenticationPolicy, HostConfig, Policy, SendMode}, webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}, }; use std::{ @@ -21,7 +21,10 @@ use std::{ }, time::{Duration, Instant}, }; -use tokio::{sync::Mutex, time::interval}; +use tokio::{ + sync::{Mutex, mpsc}, + time::interval, +}; const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); @@ -70,7 +73,11 @@ impl OmikronConnection { id: u64, description: Option<&str>, state: Arc, + authenticated: bool, ) -> Option> { + if !authenticated { + return None; + } let peer_capabilities = PeerCapabilities::from_identification_description(description).ok()?; Some(Arc::new(Self { @@ -87,7 +94,6 @@ impl OmikronConnection { &self.peer_capabilities } - pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, @@ -158,7 +164,12 @@ impl OmikronConnection { async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { log_cv_in!(PrintType::Omikron, &value); - if let Some((_, task)) = self.waiting_tasks.remove(&value.get_id()) { + if value.is_type(CommunicationType::Relay) { + return self.dispatch(value).await; + } + if let Some(message_id) = value.id() + && let Some((_, task)) = self.waiting_tasks.remove(&message_id) + { let _ = (task.task)(self.clone(), value); return Ok(()); } @@ -168,6 +179,36 @@ impl OmikronConnection { async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { let id = self.id as i64; let state = self.state.clone(); + if value.is_type(CommunicationType::Relay) { + let value = crate::transport::relay_router::ensure_relay_frame_id(value); + let request_id = value.id(); + let result = crate::transport::relay_router::route_from_omikron(id, value).await; + let response = match &result { + Ok(()) => request_id.map(|request_id| { + CommunicationValue::new(CommunicationType::Success).with_id(request_id) + }), + Err(error) => { + log_err!(id, PrintType::Omega, "Relay routing failed: {}", error); + request_id.map(|request_id| { + CommunicationValue::new( + crate::transport::relay_router::error_response_type(error), + ) + .with_id(request_id) + }) + } + }; + if let Some(response) = response + && let Err(send_error) = self.clone().send(&response).await + { + log_err!( + id, + PrintType::Omega, + "Relay routing response failed: {}", + send_error + ); + } + return result.map_err(|error| crate::error::OmegaError::Transport(error.to_string())); + } match value.get_comm_type_enum() { Some(CommunicationType::ShortenLink) => { crate::transport::handlers::links::shorten(self, value).await @@ -179,9 +220,6 @@ impl OmikronConnection { crate::transport::handlers::presence::user_disconnected(state, self, value, id) .await } - Some(CommunicationType::SetUserState) => { - crate::transport::handlers::presence::set_user_state(state, self, value, id).await - } Some(CommunicationType::IotaConnected) => { crate::transport::handlers::presence::iota_connected(state, self, value, id).await } @@ -297,6 +335,47 @@ impl OmikronConnection { } Ok(()) } + + pub(crate) async fn await_response( + self: Arc, + value: &CommunicationValue, + timeout: Duration, + ) -> OmikronResult { + let (tx, mut rx) = mpsc::channel(1); + let message_id = value.id().unwrap_or_default(); + let task_tx = tx.clone(); + self.waiting_tasks.insert( + message_id, + WaitingTask { + task: Box::new(move |_, response| { + let task_tx = task_tx.clone(); + tokio::spawn(async move { + let _ = task_tx.send(response).await; + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + if let Err(error) = self.clone().send(value).await { + self.waiting_tasks.remove(&message_id); + return Err(error); + } + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response)) => Ok(response), + Ok(None) => Err(crate::error::OmegaError::Transport( + "Relay response channel closed".into(), + )), + Err(_) => { + self.waiting_tasks.remove(&message_id); + Err(crate::error::OmegaError::Transport( + "Relay response timed out".into(), + )) + } + } + } pub(crate) async fn send_error_response( self: Arc, message_id: u32, @@ -439,16 +518,18 @@ pub async fn start(port: u16, state: Arc) -> Result<(), Box Option { + let (kind, id) = match self { + Self::User(id) => (USER_TARGET_KIND, id), + Self::Iota(id) => (IOTA_TARGET_KIND, id), + }; + (id > 0 && id <= TARGET_ID_MASK).then_some(kind | id) + } + + pub fn from_wire_id(value: u64) -> Option { + let id = value & TARGET_ID_MASK; + if id == 0 || value & !(TARGET_KIND_MASK | TARGET_ID_MASK) != 0 { + return None; + } + match value & TARGET_KIND_MASK { + USER_TARGET_KIND => Some(Self::User(id)), + IOTA_TARGET_KIND => Some(Self::Iota(id)), + _ => None, + } + } + + pub const fn id(self) -> u64 { + match self { + Self::User(id) | Self::Iota(id) => id, + } + } +} + +#[derive(Debug, Error)] +pub enum RelayRouteError { + #[error("relay has no destination Iota")] + MissingDestinationIota, + #[error("relay has an outer sender")] + OuterSenderPresent, + #[error("relay has invalid route target {0}")] + InvalidDestinationTarget(u64), + #[error("relay destination Iota is outside Omega's ID range")] + DestinationIotaOutOfRange, + #[error("destination Iota is offline")] + IotaOffline, + #[error("destination Omikron is offline")] + OmikronOffline, + #[error("relay route resolves back to source Omikron")] + RouteLoop, + #[error(transparent)] + Relay(#[from] RelayError), + #[error("sending relay to destination Omikron failed: {0}")] + Send(String), +} + +pub fn ensure_relay_frame_id(frame: CommunicationValue) -> CommunicationValue { + if frame.id().is_some_and(|id| id != 0) { + return frame; + } + let id = NEXT_RELAY_FRAME_ID.fetch_add(1, Ordering::Relaxed).max(1); + frame.with_id(id) +} + +pub fn error_response_type(error: &RelayRouteError) -> CommunicationType { + match error { + RelayRouteError::IotaOffline | RelayRouteError::OmikronOffline => { + CommunicationType::ErrorNoIota + } + RelayRouteError::Send(_) => CommunicationType::ErrorInternal, + RelayRouteError::MissingDestinationIota + | RelayRouteError::OuterSenderPresent + | RelayRouteError::InvalidDestinationTarget(_) + | RelayRouteError::DestinationIotaOutOfRange + | RelayRouteError::RouteLoop + | RelayRouteError::Relay(_) => CommunicationType::ErrorInvalidData, + } +} + +pub async fn route_from_omikron( + source_omikron_id: i64, + frame: CommunicationValue, +) -> Result<(), RelayRouteError> { + let frame = ensure_relay_frame_id(frame); + if !frame.is_type(CommunicationType::Relay) { + return Err(RelayRouteError::Relay(RelayError::NotRelay)); + } + if frame.sender().is_some() { + return Err(RelayRouteError::OuterSenderPresent); + } + let Some(destination_wire_id) = frame.receiver() else { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay routing failed: missing destination Iota" + ); + return Err(RelayRouteError::MissingDestinationIota); + }; + let Some(RouteTarget::Iota(destination_iota)) = RouteTarget::from_wire_id(destination_wire_id) + else { + return Err(RelayRouteError::InvalidDestinationTarget( + destination_wire_id, + )); + }; + let destination_iota_i64 = + i64::try_from(destination_iota).map_err(|_| RelayRouteError::DestinationIotaOutOfRange)?; + let frame = forward_relay_frame(&frame, destination_wire_id)?; + + let Some(destination_omikron) = + omikron_manager::get_iota_primary_omikron_connection(destination_iota_i64) + else { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay destination Iota {} is offline", + destination_iota + ); + return Err(RelayRouteError::IotaOffline); + }; + if destination_omikron == source_omikron_id { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay route loop for destination Iota {} and Omikron {}", + destination_iota, + destination_omikron + ); + return Err(RelayRouteError::RouteLoop); + } + let Some(connection) = omikron_manager::get_connected_omikron(destination_omikron) else { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay destination Iota {} resolves to disconnected Omikron {}", + destination_iota, + destination_omikron + ); + return Err(RelayRouteError::OmikronOffline); + }; + let response = connection + .await_response(&frame, Duration::from_secs(20)) + .await + .map_err(|error| { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay send to destination Iota {} via Omikron {} failed: {}", + destination_iota, + destination_omikron, + error + ); + RelayRouteError::Send(error.to_string()) + })?; + if response.is_type(CommunicationType::Success) { + Ok(()) + } else { + Err(RelayRouteError::Send(format!( + "destination Omikron rejected the Relay with {}", + response.get_type() + ))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mtp::codec::DataValue; + + fn wire(target: RouteTarget) -> u64 { + let Some(value) = target.wire_id() else { + panic!("valid route target was rejected"); + }; + value + } + + fn relay_frame() -> CommunicationValue { + CommunicationValue::new(CommunicationType::Relay) + .without_sender() + .with_receiver(wire(RouteTarget::Iota(42))) + .with_payload(DataValue::Bytes(vec![1, 2, 3, 4])) + } + + #[test] + fn forwarding_preserves_relay_payload_and_next_hop() { + let frame = relay_frame().with_receiver(wire(RouteTarget::Iota(7))); + let result = forward_relay_frame(&frame, wire(RouteTarget::Iota(42))); + assert!(result.is_ok()); + let Ok(forwarded) = result else { return }; + assert_eq!(forwarded.receiver(), Some(wire(RouteTarget::Iota(42)))); + assert_eq!(forwarded.sender(), None); + assert_eq!(forwarded.payload(), frame.payload()); + assert_eq!(forwarded.id(), frame.id()); + } + + #[tokio::test] + async fn outer_sender_is_rejected_before_route_lookup() { + let frame = relay_frame().with_sender(9); + let result = route_from_omikron(1, frame).await; + assert!(matches!(result, Err(RelayRouteError::OuterSenderPresent))); + } + + #[tokio::test] + async fn missing_destination_is_rejected_before_route_lookup() { + let frame = relay_frame().without_receiver(); + let result = route_from_omikron(1, frame).await; + assert!(matches!( + result, + Err(RelayRouteError::MissingDestinationIota) + )); + } + + #[tokio::test] + async fn user_route_target_is_rejected_by_opaque_omega_router() { + let frame = relay_frame().with_receiver(wire(RouteTarget::User(42))); + let result = route_from_omikron(1, frame).await; + assert!(matches!( + result, + Err(RelayRouteError::InvalidDestinationTarget(_)) + )); + } + + #[tokio::test] + async fn unavailable_destination_is_reported_as_iota_offline() { + let result = route_from_omikron(1, relay_frame()).await; + assert!(matches!(result, Err(RelayRouteError::IotaOffline))); + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs index 4276cc0..9bd211a 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -10,6 +10,7 @@ use std::{ use ansi_term::Color; use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version}; +use crate::transport::connection::MtpValueCompat; use crate::util::file_util::get_directory; static LOGGER: OnceLock> = OnceLock::new(); @@ -250,11 +251,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String { .unwrap_or_else(|| cv.get_type().to_string()); parts.push(format!("{} (id={})", comm_type, cv.get_id())); - let data = cv.data(); + let data = cv.data().unwrap_or(&[]); let formated_data = format_data_container( data.iter().map(|(k, v)| (*k, v.clone())).collect(), - Version(1, 0), + Version(3, 0), ); parts.push(format!("{}", formated_data)); From b814c3776dde04f3108611ea6f66f9b42d0c8fdb Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 18 Aug 2026 22:37:14 +0200 Subject: [PATCH 214/220] [WIP] 0.3.0 mtp update --- Cargo.lock | 335 ++++++++++++++---------- Cargo.toml | 3 +- src/db/user_repo.rs | 39 ++- src/server/api.rs | 12 +- src/sql/user_online_tracker.rs | 32 ++- src/state.rs | 40 ++- src/transport/connection.rs | 49 +++- src/transport/handlers/account.rs | 316 ++++++++++++++++++---- src/transport/handlers/links.rs | 8 +- src/transport/handlers/notifications.rs | 4 +- src/transport/handlers/presence.rs | 21 +- src/transport/handlers/register.rs | 4 +- src/transport/handlers/states.rs | 6 +- src/transport/handlers/user_data.rs | 37 ++- src/transport/mod.rs | 1 + src/transport/omikron_connection.rs | 97 ++++++- src/transport/omikron_manager.rs | 35 ++- src/transport/relay_router.rs | 243 +++++++++++++++++ src/util/logger.rs | 5 +- 19 files changed, 1009 insertions(+), 278 deletions(-) create mode 100644 src/transport/relay_router.rs diff --git a/Cargo.lock b/Cargo.lock index 1e03155..cd6c90d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + [[package]] name = "asn1-rs" version = "0.7.2" @@ -56,7 +68,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror", + "thiserror 2.0.20", "time", ] @@ -85,9 +97,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", @@ -175,6 +187,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -223,9 +244,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.1" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -740,9 +761,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flate2" @@ -800,9 +821,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -815,9 +836,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -825,15 +846,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -853,38 +874,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -936,9 +957,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "a9f37a958b41b3b19ee2707c06439c0e9e547e847223eb791ecb0cb821c65e27" dependencies = [ "atomic-waker", "bytes", @@ -1126,9 +1147,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -1223,9 +1244,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1237,9 +1258,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1250,9 +1271,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1264,16 +1285,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1284,15 +1306,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" dependencies = [ "displaydoc", "icu_locale_core", @@ -1376,7 +1398,7 @@ dependencies = [ "jni-sys", "log", "simd_cesu8", - "thiserror", + "thiserror 2.0.20", "walkdir", "windows-link", ] @@ -1425,9 +1447,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ "cfg-if", "futures-util", @@ -1482,14 +1504,14 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ "bitflags", "libc", "plain", - "redox_syscall 0.9.1", + "redox_syscall 0.9.2", ] [[package]] @@ -1504,9 +1526,9 @@ dependencies = [ [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lock_api" @@ -1642,8 +1664,8 @@ dependencies = [ [[package]] name = "mtp" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-client", "mtp-codec", @@ -1658,8 +1680,8 @@ dependencies = [ [[package]] name = "mtp-client" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-codec", "mtp-common", @@ -1671,8 +1693,8 @@ dependencies = [ [[package]] name = "mtp-codec" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "base64 0.23.1", "byteorder", @@ -1680,25 +1702,27 @@ dependencies = [ "mtp-crypto", "mtp-type-map", "rand 0.10.2", + "thiserror 2.0.20", ] [[package]] name = "mtp-common" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "quinn", "rustls", - "thiserror", + "thiserror 2.0.20", "wtransport", ] [[package]] name = "mtp-crypto" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ - "base64 0.23.1", + "argon2", + "base64 0.22.1", "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", @@ -1706,36 +1730,37 @@ dependencies = [ "ml-dsa", "mlkem-tls", "rand 0.10.2", - "rand_core 0.10.1", + "rand_core 0.6.4", "rustls", "serde", "sha2 0.11.0", - "thiserror", + "thiserror 1.0.69", "tokio", "zeroize", ] [[package]] name = "mtp-files" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-crypto", "rand 0.10.2", - "thiserror", + "thiserror 2.0.20", "zeroize", ] [[package]] name = "mtp-host" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", "mtp-transport", "rand 0.10.2", + "thiserror 2.0.20", "tokio", "tracing", "wtransport", @@ -1743,13 +1768,14 @@ dependencies = [ [[package]] name = "mtp-transport" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "async-trait", "mtp-codec", "mtp-common", "mtp-crypto", + "rand 0.10.2", "rcgen", "rustls", "rustls-native-certs", @@ -1757,12 +1783,13 @@ dependencies = [ "tokio", "tracing", "wtransport", + "zeroize", ] [[package]] name = "mtp-type-map" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "serde", "serde_yaml", @@ -1770,8 +1797,8 @@ dependencies = [ [[package]] name = "mtp-webserver" -version = "0.2.0" -source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b" +version = "0.3.0" +source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" dependencies = [ "async-trait", "bytes", @@ -1788,9 +1815,8 @@ dependencies = [ "mtp-host", "mtp-transport", "quinn", - "rand 0.10.2", "rustls", - "thiserror", + "thiserror 2.0.20", "tokio", "tokio-rustls", "tokio-stream", @@ -1841,9 +1867,9 @@ checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -1903,7 +1929,7 @@ dependencies = [ "sqlx", "strum", "strum_macros", - "thiserror", + "thiserror 2.0.20", "tokio", "uuid", "zip", @@ -1956,6 +1982,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "pbkdf2" version = "0.13.0" @@ -2039,9 +2076,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "plain" @@ -2062,15 +2099,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -2120,7 +2157,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror", + "thiserror 2.0.20", "tokio", "tracing", "web-time", @@ -2128,9 +2165,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.16" +version = "0.11.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" dependencies = [ "aws-lc-rs", "bytes", @@ -2145,7 +2182,7 @@ dependencies = [ "rustls-pki-types", "rustls-platform-verifier", "slab", - "thiserror", + "thiserror 2.0.20", "tinyvec", "tracing", "web-time", @@ -2238,9 +2275,9 @@ dependencies = [ [[package]] name = "rcgen" -version = "0.14.8" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57f6d249aad744e274e682777a50283a225a32705394ee6d5fcc01efa25e4055" +checksum = "091e7a8e7d86e6feb87a27ce8e2cba29d49eff9507afeebefab7eeb2ca667fb4" dependencies = [ "aws-lc-rs", "pem", @@ -2262,9 +2299,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07507be7b4a5f9f26eeb41eeaebb1f5a7ff29dfb29739facc21d35bf8b11c21e" +checksum = "f1c93da5bb2c5d4e6c0ef7abeead62c89169a0a4882bfb83ac892f2423aea2fe" dependencies = [ "bitflags", ] @@ -2432,9 +2469,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" dependencies = [ "aws-lc-rs", "ring", @@ -2804,7 +2841,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror", + "thiserror 2.0.20", "tokio", "tokio-stream", "tracing", @@ -2886,7 +2923,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.20", "tracing", "whoami", ] @@ -2923,7 +2960,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.20", "tracing", "whoami", ] @@ -2947,7 +2984,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror", + "thiserror 2.0.20", "tracing", "url", ] @@ -3058,18 +3095,38 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -3109,9 +3166,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -3365,9 +3422,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.24.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -3419,9 +3476,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -3432,9 +3489,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" dependencies = [ "js-sys", "wasm-bindgen", @@ -3442,9 +3499,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3452,9 +3509,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", @@ -3465,18 +3522,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" dependencies = [ "js-sys", "wasm-bindgen", @@ -3727,15 +3784,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "wtransport" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea4aacf790813ee1956751491800537f4e04af7557b7b370501ccbfbc85963e4" +checksum = "b4273ce3157a3262a68665f8d3f20a0ac0c5b8a69ffd67f05ae986832ebec036" dependencies = [ "bytes", "pem", @@ -3746,7 +3803,7 @@ dependencies = [ "rustls-pki-types", "sha2 0.11.0", "socket2", - "thiserror", + "thiserror 2.0.20", "time", "tokio", "tracing", @@ -3757,13 +3814,13 @@ dependencies = [ [[package]] name = "wtransport-proto" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11" +checksum = "aad9059572c7dbd6901ccef37f3b7321678cd708dcf58a64b1921dbeab7bfede" dependencies = [ "httlib-huffman", "octets", - "thiserror", + "thiserror 2.0.20", "url", ] @@ -3794,7 +3851,7 @@ dependencies = [ "oid-registry", "ring", "rusticata-macros", - "thiserror", + "thiserror 2.0.20", "time", ] @@ -3894,9 +3951,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -3905,9 +3962,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" dependencies = [ "yoke", "zerofrom", @@ -3916,13 +3973,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "9f212a141d820099d57ffafb9569be9617a6f27d3dc881fbee8fb56642f917a9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 3b4f6a3..28e732a 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,10 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ +mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ "crypto", "files", + "raw", "web-server", ] } diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index df99a18..79b9034 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -380,27 +380,44 @@ pub async fn delete_user_with_pending_erasure(id: UserId) -> Result = row.get("iota_id"); if let Some(iota_id) = iota_id { - sqlx::query("INSERT IGNORE INTO pending_iota_user_erasure (user_id, iota_id) VALUES (?, ?)") - .bind(id.0) - .bind(iota_id) - .execute(&mut *tx) - .await?; + sqlx::query( + "INSERT IGNORE INTO pending_iota_user_erasure (user_id, iota_id) VALUES (?, ?)", + ) + .bind(id.0) + .bind(iota_id) + .execute(&mut *tx) + .await?; } - sqlx::query("DELETE FROM registration_leases WHERE user_id = ?").bind(id.0).execute(&mut *tx).await?; - sqlx::query("DELETE FROM users WHERE id = ?").bind(id.0).execute(&mut *tx).await?; + sqlx::query("DELETE FROM registration_leases WHERE user_id = ?") + .bind(id.0) + .execute(&mut *tx) + .await?; + sqlx::query("DELETE FROM users WHERE id = ?") + .bind(id.0) + .execute(&mut *tx) + .await?; tx.commit().await?; Ok(iota_id.map(IotaId::from)) } pub async fn pending_erasures_for_iota(iota_id: IotaId) -> Result> { let rows = sqlx::query("SELECT user_id FROM pending_iota_user_erasure WHERE iota_id = ?") - .bind(iota_id.0).fetch_all(&pool().await?).await?; - Ok(rows.into_iter().map(|row| UserId::from(row.get::("user_id"))).collect()) + .bind(iota_id.0) + .fetch_all(&pool().await?) + .await?; + Ok(rows + .into_iter() + .map(|row| UserId::from(row.get::("user_id"))) + .collect()) } pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Result { - let result = sqlx::query("DELETE FROM pending_iota_user_erasure WHERE user_id = ? AND iota_id = ?") - .bind(user_id.0).bind(iota_id.0).execute(&pool().await?).await?; + let result = + sqlx::query("DELETE FROM pending_iota_user_erasure WHERE user_id = ? AND iota_id = ?") + .bind(user_id.0) + .bind(iota_id.0) + .execute(&pool().await?) + .await?; Ok(result.rows_affected() == 1) } diff --git a/src/server/api.rs b/src/server/api.rs index 0319607..23debe4 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -85,11 +85,13 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { let user = get_by_user_id(UserId::from(id)).await?; match user.iota_id { Some(iota_id) => get_iota_primary_omikron_connection(iota_id.0), - None => get_random_omikron() - .await - .map_err(|_| OmegaError::NotFound)? - .get_omikron_id() - .await, + None => { + get_random_omikron() + .await + .map_err(|_| OmegaError::NotFound)? + .get_omikron_id() + .await + } } .ok_or(OmegaError::NotFound)? }; diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index 8017c3d..be3e87c 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -1044,20 +1044,24 @@ mod tests { assert_eq!(tracker.subscribers(22).len(), 1); tracker.remove_session(7, 3, 42); assert!(tracker.check_index_consistency().is_ok()); - assert!(tracker - .routes - .read() - .unwrap() - .indices - .sessions_by_user - .is_empty()); - assert!(tracker - .routes - .read() - .unwrap() - .indices - .targets_by_subscriber - .is_empty()); + assert!( + tracker + .routes + .read() + .unwrap() + .indices + .sessions_by_user + .is_empty() + ); + assert!( + tracker + .routes + .read() + .unwrap() + .indices + .targets_by_subscriber + .is_empty() + ); } #[test] diff --git a/src/state.rs b/src/state.rs index 075d268..02c899b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,10 +1,13 @@ use crate::sql::user_online_tracker::PresenceTracker; -use std::sync::Arc; use dashmap::DashMap; +use std::sync::Arc; use std::time::{Duration, Instant}; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum AccountChallengeOperation { Attach, Delete } +pub enum AccountChallengeOperation { + Attach, + Delete, +} #[derive(Clone, Debug)] pub struct AccountChallenge { @@ -30,15 +33,38 @@ impl Default for OmegaState { } impl OmegaState { - pub fn issue_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64) -> u64 { + pub fn issue_challenge( + &self, + operation: AccountChallengeOperation, + user_id: i64, + requester_iota_id: i64, + ) -> u64 { let nonce = rand::random::(); - self.challenges.insert((operation, user_id, requester_iota_id), AccountChallenge { operation, user_id, requester_iota_id, nonce, created_at: Instant::now() }); + self.challenges.insert( + (operation, user_id, requester_iota_id), + AccountChallenge { + operation, + user_id, + requester_iota_id, + nonce, + created_at: Instant::now(), + }, + ); nonce } - pub fn consume_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64, nonce: u64) -> bool { - self.challenges.remove(&(operation, user_id, requester_iota_id)).is_some_and(|(_, value)| - value.nonce == nonce && value.created_at.elapsed() <= Duration::from_secs(120)) + pub fn consume_challenge( + &self, + operation: AccountChallengeOperation, + user_id: i64, + requester_iota_id: i64, + nonce: u64, + ) -> bool { + self.challenges + .remove(&(operation, user_id, requester_iota_id)) + .is_some_and(|(_, value)| { + value.nonce == nonce && value.created_at.elapsed() <= Duration::from_secs(120) + }) } } diff --git a/src/transport/connection.rs b/src/transport/connection.rs index eff0bb1..6cada3e 100644 --- a/src/transport/connection.rs +++ b/src/transport/connection.rs @@ -1 +1,48 @@ -pub(crate) use super::omikron_connection::OmikronConnection; +pub(crate) use super::omikron_connection::{OmikronConnection, OmikronResult}; + +use mtp::codec::{CommunicationValue, DataValue}; + +pub(crate) trait MtpValueCompat { + fn get_id(&self) -> u32; + fn get_sender(&self) -> u64; + fn get_receiver(&self) -> u64; +} + +impl MtpValueCompat for CommunicationValue { + fn get_id(&self) -> u32 { + self.id().unwrap_or_default() + } + + fn get_sender(&self) -> u64 { + self.sender().unwrap_or_default() + } + + fn get_receiver(&self) -> u64 { + self.receiver().unwrap_or_default() + } +} + +pub(crate) trait OptionalDataValueCompat { + fn as_number(&self) -> Option; + fn as_signed_number(&self) -> Option; + fn as_str(&self) -> Option<&str>; + fn as_bytes(&self) -> Option>; +} + +impl OptionalDataValueCompat for Option<&DataValue> { + fn as_number(&self) -> Option { + self.and_then(|value| value.as_number()) + } + + fn as_signed_number(&self) -> Option { + self.and_then(|value| value.as_signed_number()) + } + + fn as_str(&self) -> Option<&str> { + self.and_then(|value| value.as_str()) + } + + fn as_bytes(&self) -> Option> { + self.and_then(|value| value.as_bytes()) + } +} diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs index 360076d..0374f78 100644 --- a/src/transport/handlers/account.rs +++ b/src/transport/handlers/account.rs @@ -1,10 +1,15 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, state::AccountChallengeOperation, }; -use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, crypto::{verify_ed25519, verify_ml_dsa}}; +use mtp::{ + codec::{CommunicationType, CommunicationValue, DataType, DataValue}, + crypto::{verify_ed25519, verify_ml_dsa}, +}; use std::sync::Arc; async fn delete( @@ -67,14 +72,22 @@ pub async fn release_from_iota( let previous_iota = user.iota_id; match user_repo::change_iota_id(user.id, None).await { Ok(()) => { - if let Some(iota) = previous_iota { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } - connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await - }, - Err(error) => connection - .send(&CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) - .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))) - .await, + if let Some(iota) = previous_iota { + crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; + } + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await + } } } @@ -87,43 +100,121 @@ fn lifecycle_payload(domain: &[u8], user_id: i64, iota_id: i64, nonce: u64) -> V payload } -pub async fn attach_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { - return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; +pub async fn attach_begin( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; }; - if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { - return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; + if user_repo::get_by_user_id(UserId::from(user_id)) + .await + .is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; } let requester = value.get_sender() as i64; - let nonce = connection.state().issue_challenge(AccountChallengeOperation::Attach, user_id, requester); - connection.send(&CommunicationValue::new(CommunicationType::AttachUserChallenge).with_id(value.get_id()) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) - .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await + let nonce = + connection + .state() + .issue_challenge(AccountChallengeOperation::Attach, user_id, requester); + connection + .send( + &CommunicationValue::new(CommunicationType::AttachUserChallenge) + .with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), + ) + .await } -pub async fn attach_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; +pub async fn attach_complete( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; let requester = value.get_sender() as i64; - let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; + let Some(nonce) = value + .get_data(DataType::ServerNonce) + .as_signed_number() + .and_then(|v| u64::try_from(v).ok()) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; let signature = value.get_data(DataType::Signature).as_bytes(); let pq_signature = value.get_data(DataType::PqSignature).as_bytes(); - let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; - if !connection.state().consume_challenge(AccountChallengeOperation::Attach, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } - let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; + if !connection.state().consume_challenge( + AccountChallengeOperation::Attach, + user_id, + requester, + nonce, + ) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce); - if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() + || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await; + } let previous_iota = user.iota_id; match user_repo::change_iota_id(user.id, Some(IotaId::from(requester))).await { Ok(()) => { - if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } + if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { + crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; + } crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; - connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await - }, - Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Err(_) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .await + } } } -async fn complete_delete(connection: Arc, value: CommunicationValue, user_id: UserId) -> OmikronResult<()> { +async fn complete_delete( + connection: Arc, + value: CommunicationValue, + user_id: UserId, +) -> OmikronResult<()> { match user_repo::delete_user_with_pending_erasure(user_id).await { Ok(iota_id) => { let cleanup_pending = iota_id.is_some(); @@ -131,45 +222,160 @@ async fn complete_delete(connection: Arc, value: Communicatio crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id.0).await; crate::transport::omikron_manager::deliver_pending_erasures(iota_id.0).await; } - connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()) - .add_typed_default(DataType::CleanupPending, DataValue::Bool(cleanup_pending))).await + connection + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.get_id()) + .add_typed_default( + DataType::CleanupPending, + DataValue::Bool(cleanup_pending), + ), + ) + .await + } + Err(crate::error::OmegaError::NotFound) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await + } + Err(error) => { + connection + .send( + &CommunicationValue::new(CommunicationType::ErrorInternal) + .with_id(value.get_id()) + .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), + ) + .await } - Err(crate::error::OmegaError::NotFound) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await, - Err(error) => connection.send(&CommunicationValue::new(CommunicationType::ErrorInternal).with_id(value.get_id()).add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))).await, } } -pub async fn delete_credential_begin(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { - return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; +pub async fn delete_credential_begin( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; }; - if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; } + if user_repo::get_by_user_id(UserId::from(user_id)) + .await + .is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + } let requester = value.get_sender() as i64; - let nonce = connection.state().issue_challenge(AccountChallengeOperation::Delete, user_id, requester); - connection.send(&CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge).with_id(value.get_id()) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) - .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await + let nonce = + connection + .state() + .issue_challenge(AccountChallengeOperation::Delete, user_id, requester); + connection + .send( + &CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge) + .with_id(value.get_id()) + .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) + .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), + ) + .await } -pub async fn delete_credential_complete(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; +pub async fn delete_credential_complete( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; let requester = value.get_sender() as i64; - let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; - let (Some(signature), Some(pq_signature)) = (value.get_data(DataType::Signature).as_bytes(), value.get_data(DataType::PqSignature).as_bytes()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }; - if !connection.state().consume_challenge(AccountChallengeOperation::Delete, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; } - let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }; + let Some(nonce) = value + .get_data(DataType::ServerNonce) + .as_signed_number() + .and_then(|v| u64::try_from(v).ok()) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; + let (Some(signature), Some(pq_signature)) = ( + value.get_data(DataType::Signature).as_bytes(), + value.get_data(DataType::PqSignature).as_bytes(), + ) else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + }; + if !connection.state().consume_challenge( + AccountChallengeOperation::Delete, + user_id, + requester, + nonce, + ) { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .await; + } + let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .await; + }; let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce); - if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; } + if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() + || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() + { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await; + } complete_delete(connection, value, user.id).await } -pub async fn erase_hosted_user_data_ack(connection: Arc, value: CommunicationValue) -> OmikronResult<()> { - let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; }; +pub async fn erase_hosted_user_data_ack( + connection: Arc, + value: CommunicationValue, +) -> OmikronResult<()> { + let Some(user_id) = value + .get_data(DataType::UserId) + .as_signed_number() + .and_then(|v| i64::try_from(v).ok()) + .filter(|v| *v > 0) + else { + return connection + .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .await; + }; let iota_id = IotaId::from(value.get_sender() as i64); match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await { - Ok(true) => connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await, - Ok(false) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await, - Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await, + Ok(true) => { + connection + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .await + } + Ok(false) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .await + } + Err(_) => { + connection + .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .await + } } } diff --git a/src/transport/handlers/links.rs b/src/transport/handlers/links.rs index a43cb99..ad45c05 100644 --- a/src/transport/handlers/links.rs +++ b/src/transport/handlers/links.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::server::short_link::add_short_link; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use std::sync::Arc; @@ -7,8 +9,8 @@ pub async fn shorten( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let link = value - .get_data(DataType::Link) + let link_data = value.get_data(DataType::Link); + let link = link_data .as_str() .ok_or(crate::error::OmegaError::InvalidResponse)?; let short = add_short_link(link) diff --git a/src/transport/handlers/notifications.rs b/src/transport/handlers/notifications.rs index 5f23474..9405bf0 100644 --- a/src/transport/handlers/notifications.rs +++ b/src/transport/handlers/notifications.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{db::notification_repo, log, models::UserId}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::type_map::TypeMap; diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index de8a275..5c631bf 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::user_repo, log_in, models::IotaId, sql::connection_status::UserStatus, state::OmegaState, }; @@ -19,7 +21,7 @@ fn parse_subscription(value: &CommunicationValue) -> Result<(i64, i64, Vec) .and_then(|id| i64::try_from(id).ok()) .filter(|id| *id > 0) .ok_or("session_id")?; - let DataValue::Array(values) = value.get_data(DataType::UserIds) else { + let Some(DataValue::Array(values)) = value.get_data(DataType::UserIds) else { return Err("user_ids"); }; @@ -51,9 +53,10 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa .map(|user| { ( user.id.0, - state - .presence - .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()), + state.presence.resolve_public_state( + user.id.0, + user.iota_id.map(|id| id.0).unwrap_or_default(), + ), ) }) .collect() @@ -349,7 +352,7 @@ pub async fn set_user_state( .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) .await; }; - if let Some(requested_user) = value.get_data_opt(DataType::UserId) { + if let Some(requested_user) = value.get_data(DataType::UserId) { let Some(requested_user_id) = requested_user .as_number() .and_then(|id| i64::try_from(id).ok()) @@ -538,12 +541,12 @@ pub async fn sync_status( omikron_id: i64, ) -> OmikronResult<()> { let request_id = value.get_id(); - let DataValue::Array(iota_values) = value.get_data(DataType::IotaIds) else { + let Some(DataValue::Array(iota_values)) = value.get_data(DataType::IotaIds) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) .await; }; - let DataValue::Array(session_values) = value.get_data(DataType::UserStates) else { + let Some(DataValue::Array(session_values)) = value.get_data(DataType::UserStates) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) .await; @@ -572,7 +575,7 @@ pub async fn sync_status( } if !connection.peer_capabilities().session_snapshot_v1 { - let DataValue::Array(user_values) = value.get_data(DataType::UserIds) else { + let Some(DataValue::Array(user_values)) = value.get_data(DataType::UserIds) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) .await; diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs index 3502269..8799893 100644 --- a/src/transport/handlers/register.rs +++ b/src/transport/handlers/register.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index d147f27..e1fc12d 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::db::user_repo; use mtp::{ codec::{CommunicationType, CommunicationValue, DataType, DataValue}, @@ -29,7 +31,7 @@ pub async fn get( ) -> OmikronResult<()> { let state = connection.state(); let legacy_peer = !connection.peer_capabilities().client_state_push_v1; - let DataValue::Array(ids) = value.get_data(DataType::UserIds) else { + let Some(DataValue::Array(ids)) = value.get_data(DataType::UserIds) else { return send_error( connection, value.get_id(), diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index e331125..1c248fa 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -1,4 +1,6 @@ -use super::super::omikron_connection::{OmikronConnection, OmikronResult}; +use super::super::connection::{ + MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, +}; use crate::{ db::{iota_repo, user_repo}, models::{IotaId, UserId}, @@ -91,7 +93,9 @@ pub async fn get_user( } state.presence.resolve_private_state(id) } else { - iota_id.map(|iota_id| state.presence.resolve_public_state(id, iota_id)).unwrap_or(crate::sql::connection_status::UserStatus::user_offline) + iota_id + .map(|iota_id| state.presence.resolve_public_state(id, iota_id)) + .unwrap_or(crate::sql::connection_status::UserStatus::user_offline) }; response = response .add_typed_default( @@ -100,10 +104,13 @@ pub async fn get_user( ) .add_typed_default( DataType::OmikronConnections, - iota_id.map(|iota_id| connections(&connection, iota_id)).unwrap_or_else(|| DataValue::Array(Vec::new())), + iota_id + .map(|iota_id| connections(&connection, iota_id)) + .unwrap_or_else(|| DataValue::Array(Vec::new())), ); if let Some(iota_id) = iota_id { - response = response.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); + response = + response.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); } if let Some(route) = route { response = response.add_typed_default( @@ -138,10 +145,14 @@ pub async fn get_iota( } else if let Some(name) = value.get_data(DataType::Username).as_str() { if let Ok(user) = user_repo::get_by_username(name).await { match user.iota_id { - Some(iota_id) => iota_repo::get_iota_by_id(iota_id) - .await - .ok() - .map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), Some(name.to_owned()))), + Some(iota_id) => iota_repo::get_iota_by_id(iota_id).await.ok().map(|iota| { + ( + iota.id.0, + iota.public_key, + Some(user.id.0), + Some(name.to_owned()), + ) + }), None => None, } } else { @@ -245,12 +256,14 @@ pub async fn change_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let Some(reset) = value.get_data(DataType::ResetToken).as_str() else { + let reset_data = value.get_data(DataType::ResetToken); + let Some(reset) = reset_data.as_str() else { return connection .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; }; - let Some(new_token) = value.get_data(DataType::NewToken).as_str() else { + let new_token_data = value.get_data(DataType::NewToken); + let Some(new_token) = new_token_data.as_str() else { return connection .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) .await; @@ -270,7 +283,9 @@ pub async fn change_iota( .await; } let result = - match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))).await { + match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))) + .await + { Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, Err(error) => Err(error), }; diff --git a/src/transport/mod.rs b/src/transport/mod.rs index 52c5fe8..3107217 100644 --- a/src/transport/mod.rs +++ b/src/transport/mod.rs @@ -3,3 +3,4 @@ pub mod connection; pub mod handlers; pub mod omikron_connection; pub mod omikron_manager; +pub mod relay_router; diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 7ad0136..82b9ece 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -10,7 +10,7 @@ use dashmap::DashMap; use mtp::{ codec::{CommunicationType, CommunicationValue}, crypto::PublicKeyBundle, - host::{AuthenticationPolicy, HostConfig, Policy, SendMode}, + host::{AuthState, AuthenticationPolicy, HostConfig, Policy, SendMode}, webserver::{MTPWebServer, WebMtpReceiver, WebMtpSender}, }; use std::{ @@ -21,7 +21,10 @@ use std::{ }, time::{Duration, Instant}, }; -use tokio::{sync::Mutex, time::interval}; +use tokio::{ + sync::{Mutex, mpsc}, + time::interval, +}; const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); @@ -70,7 +73,11 @@ impl OmikronConnection { id: u64, description: Option<&str>, state: Arc, + authenticated: bool, ) -> Option> { + if !authenticated { + return None; + } let peer_capabilities = PeerCapabilities::from_identification_description(description).ok()?; Some(Arc::new(Self { @@ -87,7 +94,6 @@ impl OmikronConnection { &self.peer_capabilities } - pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, @@ -158,7 +164,12 @@ impl OmikronConnection { async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { log_cv_in!(PrintType::Omikron, &value); - if let Some((_, task)) = self.waiting_tasks.remove(&value.get_id()) { + if value.is_type(CommunicationType::Relay) { + return self.dispatch(value).await; + } + if let Some(message_id) = value.id() + && let Some((_, task)) = self.waiting_tasks.remove(&message_id) + { let _ = (task.task)(self.clone(), value); return Ok(()); } @@ -168,6 +179,36 @@ impl OmikronConnection { async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { let id = self.id as i64; let state = self.state.clone(); + if value.is_type(CommunicationType::Relay) { + let value = crate::transport::relay_router::ensure_relay_frame_id(value); + let request_id = value.id(); + let result = crate::transport::relay_router::route_from_omikron(id, value).await; + let response = match &result { + Ok(()) => request_id.map(|request_id| { + CommunicationValue::new(CommunicationType::Success).with_id(request_id) + }), + Err(error) => { + log_err!(id, PrintType::Omega, "Relay routing failed: {}", error); + request_id.map(|request_id| { + CommunicationValue::new( + crate::transport::relay_router::error_response_type(error), + ) + .with_id(request_id) + }) + } + }; + if let Some(response) = response + && let Err(send_error) = self.clone().send(&response).await + { + log_err!( + id, + PrintType::Omega, + "Relay routing response failed: {}", + send_error + ); + } + return result.map_err(|error| crate::error::OmegaError::Transport(error.to_string())); + } match value.get_comm_type_enum() { Some(CommunicationType::ShortenLink) => { crate::transport::handlers::links::shorten(self, value).await @@ -179,9 +220,6 @@ impl OmikronConnection { crate::transport::handlers::presence::user_disconnected(state, self, value, id) .await } - Some(CommunicationType::SetUserState) => { - crate::transport::handlers::presence::set_user_state(state, self, value, id).await - } Some(CommunicationType::IotaConnected) => { crate::transport::handlers::presence::iota_connected(state, self, value, id).await } @@ -297,6 +335,47 @@ impl OmikronConnection { } Ok(()) } + + pub(crate) async fn await_response( + self: Arc, + value: &CommunicationValue, + timeout: Duration, + ) -> OmikronResult { + let (tx, mut rx) = mpsc::channel(1); + let message_id = value.id().unwrap_or_default(); + let task_tx = tx.clone(); + self.waiting_tasks.insert( + message_id, + WaitingTask { + task: Box::new(move |_, response| { + let task_tx = task_tx.clone(); + tokio::spawn(async move { + let _ = task_tx.send(response).await; + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + if let Err(error) = self.clone().send(value).await { + self.waiting_tasks.remove(&message_id); + return Err(error); + } + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response)) => Ok(response), + Ok(None) => Err(crate::error::OmegaError::Transport( + "Relay response channel closed".into(), + )), + Err(_) => { + self.waiting_tasks.remove(&message_id); + Err(crate::error::OmegaError::Transport( + "Relay response timed out".into(), + )) + } + } + } pub(crate) async fn send_error_response( self: Arc, message_id: u32, @@ -439,16 +518,18 @@ pub async fn start(port: u16, state: Arc) -> Result<(), Box Option { + let (kind, id) = match self { + Self::User(id) => (USER_TARGET_KIND, id), + Self::Iota(id) => (IOTA_TARGET_KIND, id), + }; + (id > 0 && id <= TARGET_ID_MASK).then_some(kind | id) + } + + pub fn from_wire_id(value: u64) -> Option { + let id = value & TARGET_ID_MASK; + if id == 0 || value & !(TARGET_KIND_MASK | TARGET_ID_MASK) != 0 { + return None; + } + match value & TARGET_KIND_MASK { + USER_TARGET_KIND => Some(Self::User(id)), + IOTA_TARGET_KIND => Some(Self::Iota(id)), + _ => None, + } + } + + pub const fn id(self) -> u64 { + match self { + Self::User(id) | Self::Iota(id) => id, + } + } +} + +#[derive(Debug, Error)] +pub enum RelayRouteError { + #[error("relay has no destination Iota")] + MissingDestinationIota, + #[error("relay has an outer sender")] + OuterSenderPresent, + #[error("relay has invalid route target {0}")] + InvalidDestinationTarget(u64), + #[error("relay destination Iota is outside Omega's ID range")] + DestinationIotaOutOfRange, + #[error("destination Iota is offline")] + IotaOffline, + #[error("destination Omikron is offline")] + OmikronOffline, + #[error("relay route resolves back to source Omikron")] + RouteLoop, + #[error(transparent)] + Relay(#[from] RelayError), + #[error("sending relay to destination Omikron failed: {0}")] + Send(String), +} + +pub fn ensure_relay_frame_id(frame: CommunicationValue) -> CommunicationValue { + if frame.id().is_some_and(|id| id != 0) { + return frame; + } + let id = NEXT_RELAY_FRAME_ID.fetch_add(1, Ordering::Relaxed).max(1); + frame.with_id(id) +} + +pub fn error_response_type(error: &RelayRouteError) -> CommunicationType { + match error { + RelayRouteError::IotaOffline | RelayRouteError::OmikronOffline => { + CommunicationType::ErrorNoIota + } + RelayRouteError::Send(_) => CommunicationType::ErrorInternal, + RelayRouteError::MissingDestinationIota + | RelayRouteError::OuterSenderPresent + | RelayRouteError::InvalidDestinationTarget(_) + | RelayRouteError::DestinationIotaOutOfRange + | RelayRouteError::RouteLoop + | RelayRouteError::Relay(_) => CommunicationType::ErrorInvalidData, + } +} + +pub async fn route_from_omikron( + source_omikron_id: i64, + frame: CommunicationValue, +) -> Result<(), RelayRouteError> { + let frame = ensure_relay_frame_id(frame); + if !frame.is_type(CommunicationType::Relay) { + return Err(RelayRouteError::Relay(RelayError::NotRelay)); + } + if frame.sender().is_some() { + return Err(RelayRouteError::OuterSenderPresent); + } + let Some(destination_wire_id) = frame.receiver() else { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay routing failed: missing destination Iota" + ); + return Err(RelayRouteError::MissingDestinationIota); + }; + let Some(RouteTarget::Iota(destination_iota)) = RouteTarget::from_wire_id(destination_wire_id) + else { + return Err(RelayRouteError::InvalidDestinationTarget( + destination_wire_id, + )); + }; + let destination_iota_i64 = + i64::try_from(destination_iota).map_err(|_| RelayRouteError::DestinationIotaOutOfRange)?; + let frame = forward_relay_frame(&frame, destination_wire_id)?; + + let Some(destination_omikron) = + omikron_manager::get_iota_primary_omikron_connection(destination_iota_i64) + else { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay destination Iota {} is offline", + destination_iota + ); + return Err(RelayRouteError::IotaOffline); + }; + if destination_omikron == source_omikron_id { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay route loop for destination Iota {} and Omikron {}", + destination_iota, + destination_omikron + ); + return Err(RelayRouteError::RouteLoop); + } + let Some(connection) = omikron_manager::get_connected_omikron(destination_omikron) else { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay destination Iota {} resolves to disconnected Omikron {}", + destination_iota, + destination_omikron + ); + return Err(RelayRouteError::OmikronOffline); + }; + let response = connection + .await_response(&frame, Duration::from_secs(20)) + .await + .map_err(|error| { + log_err!( + source_omikron_id, + PrintType::Omega, + "Relay send to destination Iota {} via Omikron {} failed: {}", + destination_iota, + destination_omikron, + error + ); + RelayRouteError::Send(error.to_string()) + })?; + if response.is_type(CommunicationType::Success) { + Ok(()) + } else { + Err(RelayRouteError::Send(format!( + "destination Omikron rejected the Relay with {}", + response.get_type() + ))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use mtp::codec::DataValue; + + fn wire(target: RouteTarget) -> u64 { + let Some(value) = target.wire_id() else { + panic!("valid route target was rejected"); + }; + value + } + + fn relay_frame() -> CommunicationValue { + CommunicationValue::new(CommunicationType::Relay) + .without_sender() + .with_receiver(wire(RouteTarget::Iota(42))) + .with_payload(DataValue::Bytes(vec![1, 2, 3, 4])) + } + + #[test] + fn forwarding_preserves_relay_payload_and_next_hop() { + let frame = relay_frame().with_receiver(wire(RouteTarget::Iota(7))); + let result = forward_relay_frame(&frame, wire(RouteTarget::Iota(42))); + assert!(result.is_ok()); + let Ok(forwarded) = result else { return }; + assert_eq!(forwarded.receiver(), Some(wire(RouteTarget::Iota(42)))); + assert_eq!(forwarded.sender(), None); + assert_eq!(forwarded.payload(), frame.payload()); + assert_eq!(forwarded.id(), frame.id()); + } + + #[tokio::test] + async fn outer_sender_is_rejected_before_route_lookup() { + let frame = relay_frame().with_sender(9); + let result = route_from_omikron(1, frame).await; + assert!(matches!(result, Err(RelayRouteError::OuterSenderPresent))); + } + + #[tokio::test] + async fn missing_destination_is_rejected_before_route_lookup() { + let frame = relay_frame().without_receiver(); + let result = route_from_omikron(1, frame).await; + assert!(matches!( + result, + Err(RelayRouteError::MissingDestinationIota) + )); + } + + #[tokio::test] + async fn user_route_target_is_rejected_by_opaque_omega_router() { + let frame = relay_frame().with_receiver(wire(RouteTarget::User(42))); + let result = route_from_omikron(1, frame).await; + assert!(matches!( + result, + Err(RelayRouteError::InvalidDestinationTarget(_)) + )); + } + + #[tokio::test] + async fn unavailable_destination_is_reported_as_iota_offline() { + let result = route_from_omikron(1, relay_frame()).await; + assert!(matches!(result, Err(RelayRouteError::IotaOffline))); + } +} diff --git a/src/util/logger.rs b/src/util/logger.rs index 4276cc0..9bd211a 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -10,6 +10,7 @@ use std::{ use ansi_term::Color; use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version}; +use crate::transport::connection::MtpValueCompat; use crate::util::file_util::get_directory; static LOGGER: OnceLock> = OnceLock::new(); @@ -250,11 +251,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String { .unwrap_or_else(|| cv.get_type().to_string()); parts.push(format!("{} (id={})", comm_type, cv.get_id())); - let data = cv.data(); + let data = cv.data().unwrap_or(&[]); let formated_data = format_data_container( data.iter().map(|(k, v)| (*k, v.clone())).collect(), - Version(1, 0), + Version(3, 0), ); parts.push(format!("{}", formated_data)); From 19972a023340e15add3f512aef08993833c66046 Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Wed, 19 Aug 2026 13:00:31 +0300 Subject: [PATCH 215/220] Lock file maintenance --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd6c90d..e71cd5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1665,7 +1665,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-client", "mtp-codec", @@ -1681,7 +1681,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-codec", "mtp-common", @@ -1694,7 +1694,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "base64 0.23.1", "byteorder", @@ -1708,7 +1708,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "quinn", "rustls", @@ -1719,7 +1719,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "argon2", "base64 0.22.1", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1753,7 +1753,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-codec", "mtp-common", @@ -1769,7 +1769,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "async-trait", "mtp-codec", @@ -1789,7 +1789,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "serde", "serde_yaml", @@ -1798,7 +1798,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "async-trait", "bytes", From a9094c28f8a89abc39d2475ab3385cc65aea8a9c Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Wed, 19 Aug 2026 13:00:31 +0300 Subject: [PATCH 216/220] Lock file maintenance --- Cargo.lock | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cd6c90d..e71cd5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1665,7 +1665,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-client", "mtp-codec", @@ -1681,7 +1681,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-codec", "mtp-common", @@ -1694,7 +1694,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "base64 0.23.1", "byteorder", @@ -1708,7 +1708,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "quinn", "rustls", @@ -1719,7 +1719,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "argon2", "base64 0.22.1", @@ -1742,7 +1742,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1753,7 +1753,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "mtp-codec", "mtp-common", @@ -1769,7 +1769,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "async-trait", "mtp-codec", @@ -1789,7 +1789,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "serde", "serde_yaml", @@ -1798,7 +1798,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#b331b9f6a3943d0331d8fdcb7c696d6c2bc5e8b8" +source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" dependencies = [ "async-trait", "bytes", From 4ffe8a434fe36789197ad5d93a3fc742db3f1e9e Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 20 Aug 2026 17:05:37 +0200 Subject: [PATCH 217/220] [Updt] Mtp 0.3.0 --- Cargo.lock | 514 +----------------------- Cargo.toml | 7 +- README.md | 13 + mtp-type-maps | 2 +- src/config.rs | 96 ++++- src/db/iota_repo.rs | 10 +- src/db/user_repo.rs | 49 +-- src/error.rs | 25 ++ src/identity.rs | 314 +++++++++++++++ src/main.rs | 69 ++-- src/models/mod.rs | 3 +- src/server/api.rs | 53 +-- src/server/middleware.rs | 27 +- src/server/mod.rs | 2 +- src/server/server.rs | 16 - src/server/short_link.rs | 2 +- src/server/web.rs | 47 +++ src/sql/connection_status.rs | 11 +- src/sql/user_online_tracker.rs | 123 ++---- src/state.rs | 60 ++- src/transport/connection.rs | 154 ++++++- src/transport/handlers/account.rs | 161 +++++--- src/transport/handlers/links.rs | 4 +- src/transport/handlers/notifications.rs | 139 ++++--- src/transport/handlers/presence.rs | 239 ++--------- src/transport/handlers/register.rs | 25 +- src/transport/handlers/states.rs | 23 +- src/transport/handlers/user_data.rs | 134 +++--- src/transport/omikron_connection.rs | 344 ++++++++++++++-- src/transport/omikron_manager.rs | 21 +- src/transport/relay_router.rs | 21 +- src/util/file_util.rs | 282 +------------ src/util/logger.rs | 21 +- 33 files changed, 1480 insertions(+), 1531 deletions(-) create mode 100644 src/identity.rs delete mode 100644 src/server/server.rs create mode 100644 src/server/web.rs diff --git a/Cargo.lock b/Cargo.lock index e71cd5f..2a4b6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" @@ -18,17 +12,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "aes" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" -dependencies = [ - "cipher 0.5.2", - "cpubits", - "cpufeatures 0.3.0", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -212,7 +195,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", - "zeroize", ] [[package]] @@ -233,15 +215,6 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - [[package]] name = "cc" version = "1.4.3" @@ -273,7 +246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures 0.2.17", ] @@ -296,7 +269,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20 0.9.1", - "cipher 0.4.4", + "cipher", "poly1305", "zeroize", ] @@ -308,20 +281,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout 0.1.4", + "inout", "zeroize", ] -[[package]] -name = "cipher" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" -dependencies = [ - "crypto-common 0.2.2", - "inout 0.2.2", -] - [[package]] name = "cmake" version = "0.1.58" @@ -359,22 +322,6 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[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" version = "0.10.1" @@ -391,12 +338,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpubits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -430,15 +371,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-queue" version = "0.3.13" @@ -547,12 +479,6 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" -[[package]] -name = "deflate64" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" - [[package]] name = "der" version = "0.7.10" @@ -617,7 +543,6 @@ dependencies = [ "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", - "zeroize", ] [[package]] @@ -683,15 +608,6 @@ dependencies = [ "serde", ] -[[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" @@ -765,16 +681,6 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "miniz_oxide", - "zlib-rs", -] - [[package]] name = "flume" version = "0.11.1" @@ -1199,22 +1105,6 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", ] [[package]] @@ -1223,23 +1113,12 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", "bytes", - "futures-channel", - "futures-util", "http", "http-body", "hyper", - "ipnet", - "libc", - "percent-encoding", "pin-project-lite", - "socket2", - "system-configuration", "tokio", - "tower-service", - "tracing", - "windows-registry", ] [[package]] @@ -1365,21 +1244,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "inout" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ipnet" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" - [[package]] name = "itoa" version = "1.0.18" @@ -1484,12 +1348,6 @@ dependencies = [ "spin", ] -[[package]] -name = "libbz2-rs-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" - [[package]] name = "libc" version = "0.2.189" @@ -1551,15 +1409,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-rust2" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" -dependencies = [ - "sha2 0.11.0", -] - [[package]] name = "md-5" version = "0.10.6" @@ -1576,28 +1425,12 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[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", - "simd-adler32", -] - [[package]] name = "mio" version = "1.2.2" @@ -1665,7 +1498,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-client", "mtp-codec", @@ -1681,7 +1514,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-codec", "mtp-common", @@ -1694,7 +1527,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "base64 0.23.1", "byteorder", @@ -1708,7 +1541,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "quinn", "rustls", @@ -1719,7 +1552,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "argon2", "base64 0.22.1", @@ -1742,7 +1575,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1753,7 +1586,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-codec", "mtp-common", @@ -1769,7 +1602,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "async-trait", "mtp-codec", @@ -1789,7 +1622,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "serde", "serde_yaml", @@ -1798,7 +1631,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "async-trait", "bytes", @@ -1922,17 +1755,15 @@ dependencies = [ "mtp", "once_cell", "rand 0.10.2", - "reqwest", "rustls", "serde", "serde_json", "sqlx", - "strum", - "strum_macros", "thiserror 2.0.20", "tokio", + "tokio-util", "uuid", - "zip", + "zeroize", ] [[package]] @@ -1993,16 +1824,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pbkdf2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" -dependencies = [ - "digest 0.11.3", - "hmac 0.13.0", -] - [[package]] name = "pem" version = "3.0.6" @@ -2118,12 +1939,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppmd-rust" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2306,44 +2121,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "reqwest" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "ring" version = "0.17.14" @@ -2446,7 +2223,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "jni", "log", @@ -2522,7 +2299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -2623,17 +2400,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -2713,12 +2479,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - [[package]] name = "simd_cesu8" version = "1.2.0" @@ -2918,7 +2678,7 @@ dependencies = [ "rand 0.8.7", "rsa", "serde", - "sha1 0.10.7", + "sha1", "sha2 0.10.9", "smallvec", "sqlx-core", @@ -3006,24 +2766,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3052,15 +2794,6 @@ dependencies = [ "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" @@ -3072,27 +2805,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "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 = "thiserror" version = "1.0.69" @@ -3140,7 +2852,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "js-sys", "num-conv", "powerfmt", "serde_core", @@ -3247,56 +2958,12 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "libc", "pin-project-lite", "tokio", ] -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[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.44" @@ -3329,18 +2996,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typed-path" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" - [[package]] name = "typenum" version = "1.20.1" @@ -3453,15 +3108,6 @@ dependencies = [ "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 = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3487,16 +3133,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.127" @@ -3529,16 +3165,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "web-sys" -version = "0.3.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -3605,35 +3231,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3982,81 +3579,8 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "zip" -version = "8.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" -dependencies = [ - "aes", - "bzip2", - "constant_time_eq", - "crc32fast", - "deflate64", - "flate2", - "getrandom 0.4.3", - "hmac 0.13.0", - "indexmap", - "lzma-rust2", - "memchr", - "pbkdf2", - "ppmd-rust", - "sha1 0.11.0", - "time", - "typed-path", - "zeroize", - "zopfli", - "zstd", -] - -[[package]] -name = "zlib-rs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" - [[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 28e732a..395ad06 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2024" mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ "crypto", "files", - "raw", "web-server", ] } @@ -19,7 +18,6 @@ dotenv = "0.15.0" http = "1" once_cell = "1.21.4" rand = "0.10.2" -reqwest = "0.13.4" rustls = { version = "0.23.42", default-features = false, features = [ "std", "tls12", @@ -27,11 +25,10 @@ rustls = { version = "0.23.42", default-features = false, features = [ "prefer-post-quantum", ] } sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } -strum = "0.28.0" -strum_macros = "0.28.0" tokio = { version = "*", features = ["full"] } +tokio-util = { version = "0.7.19", features = ["rt"] } uuid = { version = "1.24.0", features = ["v4", "v7"] } -zip = "8.6.0" thiserror = "2.0.19" serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" +zeroize = "1.9" diff --git a/README.md b/README.md index a1d4baf..2c05741 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ # Omega The Omega is Tensamin's central Server. It maintains the centralized user Registry & manages Omikron useage. + +Omega requires the non-empty `OMEGA_IDENTITY_SECRET` environment variable at startup. On first start it creates the protected private identity at `./omega.mk` and the matching public bundle at `./omega.mpkb`. Existing identity files are loaded fail-closed, so a malformed keyring or mismatched public bundle stops startup. A missing public bundle is rebuilt from a valid private keyring without generating a new identity. + +## MTP routing contract + +Omega preserves the MTP distinction between an absent routing field and a +present zero value on the wire. For Omega request and response operations, +both forms are rejected: + +- request IDs are required and nonzero for correlation; +- authenticated sender and receiver IDs are required and nonzero; +- fields that are optional for a particular notification remain `Option` and + are not converted to zero. diff --git a/mtp-type-maps b/mtp-type-maps index 486541b..a297dcc 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit 486541b9483356ff49ff3ec7016f87d3ecbeaa0e +Subproject commit a297dcce60bc6e84696c6a16f5fd510beb2ca643 diff --git a/src/config.rs b/src/config.rs index 66f5382..d3c7b29 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use std::{env, time::Duration}; +use thiserror::Error; #[derive(Clone, Debug)] pub struct RateLimitConfig { @@ -9,6 +10,26 @@ pub struct RateLimitConfig { pub transport_connections_per_ip: usize, } +#[derive(Clone, Debug)] +pub struct DispatchConfig { + pub omikron_handler_concurrency: usize, + pub global_handler_concurrency: usize, +} + +#[derive(Clone, Debug, Default)] +pub struct OmegaConfig { + pub rate_limits: RateLimitConfig, + pub dispatch: DispatchConfig, +} + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("invalid configuration value for {name}: {value}")] + InvalidValue { name: String, value: String }, + #[error("configuration value for {name} is not valid Unicode")] + InvalidUnicode { name: String }, +} + pub const fn cors_origin() -> &'static str { "*" } @@ -26,40 +47,75 @@ impl Default for RateLimitConfig { } impl RateLimitConfig { - pub fn from_env() -> Self { + pub fn from_env() -> Result { let defaults = Self::default(); - Self { - window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window), - general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests), + Ok(Self { + window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window)?, + general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests)?, registration_requests: env_usize( "RATE_LIMIT_REGISTRATION_REQUESTS", defaults.registration_requests, - ), + )?, transport_connections: env_usize( "RATE_LIMIT_TRANSPORT_CONNECTIONS", defaults.transport_connections, - ), + )?, transport_connections_per_ip: env_usize( "RATE_LIMIT_TRANSPORT_CONNECTIONS_PER_IP", defaults.transport_connections_per_ip, - ), + )?, + }) + } + + pub fn from_env_or_default() -> Self { + Self::from_env().unwrap_or_default() + } +} + +impl Default for DispatchConfig { + fn default() -> Self { + Self { + omikron_handler_concurrency: 32, + global_handler_concurrency: 256, } } } -fn env_usize(name: &str, fallback: usize) -> usize { - env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .unwrap_or(fallback) +impl OmegaConfig { + pub fn from_env() -> Result { + let rate_limits = RateLimitConfig::from_env()?; + let defaults = DispatchConfig::default(); + Ok(Self { + rate_limits, + dispatch: DispatchConfig { + omikron_handler_concurrency: env_usize( + "OMEGA_OMIKRON_HANDLER_CONCURRENCY", + defaults.omikron_handler_concurrency, + )?, + global_handler_concurrency: env_usize( + "OMEGA_GLOBAL_HANDLER_CONCURRENCY", + defaults.global_handler_concurrency, + )?, + }, + }) + } } -fn env_duration(name: &str, fallback: Duration) -> Duration { - env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .map(Duration::from_secs) - .unwrap_or(fallback) +fn env_usize(name: &str, fallback: usize) -> Result { + match env::var(name) { + Ok(value) => value + .parse::() + .ok() + .filter(|value| *value > 0) + .ok_or_else(|| ConfigError::InvalidValue { + name: name.into(), + value, + }), + Err(env::VarError::NotPresent) => Ok(fallback), + Err(env::VarError::NotUnicode(_)) => Err(ConfigError::InvalidUnicode { name: name.into() }), + } +} + +fn env_duration(name: &str, fallback: Duration) -> Result { + env_usize(name, fallback.as_secs() as usize).map(|value| Duration::from_secs(value as u64)) } diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs index 3eedce8..fb50432 100644 --- a/src/db/iota_repo.rs +++ b/src/db/iota_repo.rs @@ -48,20 +48,12 @@ pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> } sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id.0) - .bind(public_key.as_bytes()) + .bind(public_key.try_as_bytes()?) .execute(&pool().await?) .await?; Ok(()) } -pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> { - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(key.as_bytes()) - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} pub async fn delete_iota(id: IotaId) -> Result<()> { sqlx::query("DELETE FROM iotas WHERE id = ?") .bind(id.0) diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index 79b9034..0d10d4c 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -20,7 +20,7 @@ pub async fn get_register_id() -> Result { .unwrap_or(0); let ts = timestamp as i64; - if ts >= 1 && ts <= MAX_PROTOCOL_ID { + if (1..=MAX_PROTOCOL_ID).contains(&ts) { return Ok(UserId::from(ts)); } @@ -335,15 +335,6 @@ pub async fn change_status(id: UserId, value: String) -> Result<()> { .await } -pub async fn change_presence_preference(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET presence_preference = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} - pub async fn change_iota_id(id: UserId, value: Option) -> Result<()> { sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") .bind(value.map(|id| id.0)) @@ -360,14 +351,6 @@ pub async fn change_token(id: UserId, value: String) -> Result<()> { ) .await } -pub async fn delete_user(id: UserId) -> Result<()> { - sqlx::query("DELETE FROM users WHERE id = ?") - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} - /// Delete the central identity while retaining a durable instruction for the /// last hosting Iota. The pending row is intentionally independent of users: /// it must outlive the account row. @@ -422,8 +405,9 @@ pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Re } pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> { + let public_key = public_key.try_as_bytes()?; sqlx::query("UPDATE users SET public_key = ? WHERE id = ?") - .bind(public_key.as_bytes()) + .bind(public_key) .bind(id.0) .execute(&pool().await?) .await?; @@ -446,6 +430,7 @@ pub async fn register_complete_user( if !valid_username(&username) { return Err(OmegaError::Validation("invalid username".into())); } + let public_key_bytes = public_key.try_as_bytes()?; let mut transaction = pool().await?.begin().await?; let lease = sqlx::query( @@ -472,7 +457,7 @@ pub async fn register_complete_user( ) .bind(id.0) .bind(username.as_bytes()) - .bind(public_key.as_bytes()) + .bind(&public_key_bytes) .bind(iota_id.0) .bind(token.as_bytes()) .execute(&mut *transaction) @@ -485,20 +470,20 @@ pub async fn register_complete_user( .bind(id.0) .fetch_optional(&mut *transaction) .await?; - match existing - .map(User::try_from) - .transpose() - .map_err(OmegaError::from)? - { - Some(existing) - if existing.iota_id == Some(iota_id) + let existing_matches = match existing { + Some(existing) => { + let existing = User::try_from(existing).map_err(OmegaError::from)?; + existing.iota_id == Some(iota_id) && existing.username == username - && existing.public_key.as_bytes() == public_key.as_bytes() - && existing.token == token => - { - Ok(()) + && existing.public_key.try_as_bytes()? == public_key_bytes + && existing.token == token } - _ => Err(insert_error.into()), + None => false, + }; + if existing_matches { + Ok(()) + } else { + Err(insert_error.into()) } } }; diff --git a/src/error.rs b/src/error.rs index 9911d25..446afec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,34 @@ +use std::path::PathBuf; use thiserror::Error; +#[derive(Debug, Error)] +pub enum IdentityError { + #[error("identity storage error at {path}: {source}")] + Storage { + path: PathBuf, + #[source] + source: mtp::files::FileError, + }, + #[error("identity I/O error at {path}: {source}")] + Io { + path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("public key bundle at {path} does not match the private keyring")] + PublicBundleMismatch { path: PathBuf }, +} + #[derive(Debug, Error)] pub enum OmegaError { #[error("database pool is not initialized")] DatabaseNotInitialized, #[error("database error: {0}")] Database(sqlx::Error), + #[error("cryptographic error: {0}")] + Crypto(#[from] mtp::crypto::CryptoError), + #[error("identity error: {0}")] + Identity(#[from] IdentityError), #[error("invalid input: {0}")] Validation(String), #[error("resource not found")] @@ -45,6 +68,8 @@ impl OmegaError { Self::NotFound => http::StatusCode::NOT_FOUND, Self::DatabaseNotInitialized | Self::Database(_) + | Self::Crypto(_) + | Self::Identity(_) | Self::Transport(_) | Self::NotConnected | Self::NotAuthenticated diff --git a/src/identity.rs b/src/identity.rs new file mode 100644 index 0000000..a7a831d --- /dev/null +++ b/src/identity.rs @@ -0,0 +1,314 @@ +use crate::error::{IdentityError, Result}; +use mtp::crypto::{Keyring, PublicKeyBundle}; +use mtp::files::{ + FileError, load_keyring, load_public_key_bundle, save_keyring, save_public_key_bundle, +}; +use std::{ + fs, + path::{Path, PathBuf}, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; + +pub const KEYRING_PATH: &str = "./omega.mk"; +pub const PUBLIC_KEY_PATH: &str = "./omega.mpkb"; + +pub struct OmegaIdentity { + keyring: Arc, +} + +static PUBLIC_BUNDLE_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); + +impl OmegaIdentity { + pub fn load_or_create(passphrase: &[u8]) -> Result { + Self::load_or_create_at( + Path::new(KEYRING_PATH), + Path::new(PUBLIC_KEY_PATH), + passphrase, + ) + } + + pub(crate) fn load_or_create_at( + keyring_path: impl AsRef, + public_key_path: impl AsRef, + passphrase: &[u8], + ) -> Result { + let keyring_path = keyring_path.as_ref(); + let public_key_path = public_key_path.as_ref(); + let keyring = match load_keyring(keyring_path, passphrase) { + Ok(keyring) => keyring, + Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => { + return Self::create_at(keyring_path, public_key_path, passphrase); + } + Err(error) => { + return Err(IdentityError::Storage { + path: keyring_path.to_path_buf(), + source: error, + } + .into()); + } + }; + + let identity = Self { + keyring: Arc::new(keyring), + }; + match load_public_key_bundle(public_key_path) { + Ok(persisted_bundle) => { + identity.verify_public_bundle(public_key_path, &persisted_bundle)? + } + Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => { + Self::persist_public_bundle(&identity.public_key_bundle(), public_key_path)?; + } + Err(error) => { + return Err(IdentityError::Storage { + path: public_key_path.to_path_buf(), + source: error, + } + .into()); + } + } + Ok(identity) + } + + fn create_at(keyring_path: &Path, public_key_path: &Path, passphrase: &[u8]) -> Result { + let keyring = Keyring::generate(); + save_keyring(&keyring, keyring_path, passphrase).map_err(|error| { + IdentityError::Storage { + path: keyring_path.to_path_buf(), + source: error, + } + })?; + Self::persist_public_bundle(&keyring.public_key_bundle(), public_key_path)?; + Ok(Self { + keyring: Arc::new(keyring), + }) + } + + fn persist_public_bundle(bundle: &PublicKeyBundle, path: &Path) -> Result<()> { + let temporary_path = temporary_path(path)?; + let result = save_public_key_bundle(bundle, &temporary_path).map_err(|source| { + IdentityError::Storage { + path: path.to_path_buf(), + source, + } + }); + if let Err(error) = result { + let _ = fs::remove_file(&temporary_path); + return Err(error.into()); + } + if let Err(source) = fs::File::open(&temporary_path).and_then(|file| file.sync_all()) { + let _ = fs::remove_file(&temporary_path); + return Err(IdentityError::Io { + path: path.to_path_buf(), + source, + } + .into()); + } + if let Err(source) = fs::rename(&temporary_path, path) { + let _ = fs::remove_file(&temporary_path); + return Err(IdentityError::Io { + path: path.to_path_buf(), + source, + } + .into()); + } + Ok(()) + } + + fn verify_public_bundle(&self, path: &Path, persisted_bundle: &PublicKeyBundle) -> Result<()> { + let expected = self.keyring.public_key_bundle().try_as_bytes()?; + let actual = persisted_bundle.try_as_bytes()?; + if expected != actual { + return Err(IdentityError::PublicBundleMismatch { + path: path.to_path_buf(), + } + .into()); + } + Ok(()) + } + + pub fn keyring(&self) -> &Keyring { + &self.keyring + } + + pub fn public_key_bundle(&self) -> PublicKeyBundle { + self.keyring().public_key_bundle() + } + + pub fn clone_keyring(&self) -> Result { + let bytes = self.keyring.try_to_bytes()?; + Ok(Keyring::from_bytes(&bytes)?) + } + + #[cfg(test)] + pub(crate) fn from_keyring(keyring: Keyring) -> Self { + Self { + keyring: Arc::new(keyring), + } + } +} + +fn temporary_path(path: &Path) -> Result { + let parent = path.parent().unwrap_or_else(|| Path::new(".")); + let file_name = path.file_name().ok_or_else(|| IdentityError::Io { + path: path.to_path_buf(), + source: std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "identity path has no file name", + ), + })?; + let mut temporary_name = file_name.to_os_string(); + temporary_name.push(format!( + ".tmp-{}-{}", + std::process::id(), + PUBLIC_BUNDLE_TEMP_COUNTER.fetch_add(1, Ordering::Relaxed) + )); + Ok(parent.join(temporary_name)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::sync::atomic::{AtomicU64, Ordering}; + + fn test_directory() -> std::path::PathBuf { + static COUNTER: AtomicU64 = AtomicU64::new(0); + let id = COUNTER.fetch_add(1, Ordering::Relaxed); + let path = + std::env::temp_dir().join(format!("omega-identity-test-{}-{id}", std::process::id())); + fs::create_dir_all(&path).expect("create test directory"); + path + } + + #[test] + fn generated_identity_survives_restart_with_separate_file_formats() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let first_bundle = first.public_key_bundle().try_as_bytes().expect("bundle"); + + let keyring_bytes = fs::read(&keyring_path).expect("read keyring"); + let bundle_bytes = fs::read(&public_key_path).expect("read bundle"); + assert_eq!(&keyring_bytes[..4], b"MTMK"); + assert_eq!(&bundle_bytes[..4], b"MPKB"); + + let restarted = + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("reload identity"); + assert_eq!( + restarted + .public_key_bundle() + .try_as_bytes() + .expect("bundle"), + first_bundle + ); + assert_eq!( + load_public_key_bundle(&public_key_path) + .expect("load public bundle") + .try_as_bytes() + .expect("bundle"), + first_bundle + ); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn invalid_existing_keyring_does_not_create_a_new_identity() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + fs::write(&keyring_path, b"not-a-keyring").expect("write invalid keyring"); + + let result = + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"test-passphrase"); + assert!(result.is_err()); + assert!(!public_key_path.exists()); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn missing_public_bundle_is_repaired_without_changing_keyring() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let original_keyring = fs::read(&keyring_path).expect("read keyring"); + fs::remove_file(&public_key_path).expect("remove bundle"); + + let repaired = + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("repair bundle"); + + assert_eq!( + fs::read(&keyring_path).expect("read keyring"), + original_keyring + ); + assert_eq!( + repaired.public_key_bundle().try_as_bytes().expect("bundle"), + load_public_key_bundle(&public_key_path) + .expect("load bundle") + .try_as_bytes() + .expect("bundle") + ); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn mismatched_public_bundle_stops_startup() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let other_keyring = Keyring::generate(); + save_public_key_bundle(&other_keyring.public_key_bundle(), &public_key_path) + .expect("save mismatched bundle"); + + assert!(matches!( + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase), + Err(crate::OmegaError::Identity( + IdentityError::PublicBundleMismatch { .. } + )) + )); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn wrong_passphrase_does_not_replace_existing_keyring() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let original_keyring = fs::read(&keyring_path).expect("read keyring"); + + assert!( + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"wrong-passphrase") + .is_err() + ); + assert_eq!( + fs::read(&keyring_path).expect("read keyring"), + original_keyring + ); + + fs::remove_dir_all(directory).expect("remove test directory"); + } +} diff --git a/src/main.rs b/src/main.rs index 16b850e..e04238c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod api; mod config; mod db; pub mod error; +mod identity; mod models; mod server; mod sql; @@ -17,45 +18,18 @@ use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; +use crate::{config::OmegaConfig, server::middleware}; use dotenv::from_path; -use mtp::crypto::Keyring; -use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; -use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; use std::time::Duration; use tokio::time::interval; - -const KEYRING_PATH: &str = "./omega.mk"; - -static KEYRING: Lazy = Lazy::new(|| { - load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| { - let kr = Keyring::generate(); - if let Err(error) = save_keyring_raw(&kr, KEYRING_PATH) { - eprintln!("Failed to save generated keyring: {error}"); - } - if let Err(error) = save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) { - eprintln!("Failed to save generated public key bundle: {error}"); - } - eprintln!("Generated new keyring at {}", KEYRING_PATH); - kr - }) -}); - -pub fn get_keyring() -> &'static Keyring { - &KEYRING -} -pub fn load_keyring() -> Keyring { - Keyring::from_bytes(&KEYRING.to_bytes()).unwrap_or_else(|error| { - eprintln!("Failed to clone keyring: {error}"); - Keyring::generate() - }) -} +use zeroize::Zeroizing; #[tokio::main] async fn main() { - if let Err(_) = default_provider().install_default() { + if default_provider().install_default().is_err() { println!("Error loading Provider"); return; } @@ -64,6 +38,39 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); + let identity_secret = match env::var("OMEGA_IDENTITY_SECRET") { + Ok(secret) if !secret.is_empty() => secret, + Ok(_) => { + log!("[FATAL] OMEGA_IDENTITY_SECRET must not be empty"); + return; + } + Err(error) => { + log!("[FATAL] Unable to load OMEGA_IDENTITY_SECRET: {}", error); + return; + } + }; + let identity_secret = Zeroizing::new(identity_secret); + let config = match OmegaConfig::from_env() { + Ok(config) => config, + Err(error) => { + log!("[FATAL] Omega configuration is invalid: {}", error); + return; + } + }; + if middleware::initialize_config(config.rate_limits.clone()).is_err() { + log!("[FATAL] Omega rate-limit configuration was initialized more than once"); + return; + } + let identity = match identity::OmegaIdentity::load_or_create(identity_secret.as_bytes()) { + Ok(identity) => identity, + Err(error) => { + log!("[FATAL] Omega identity initialization failed: {}", error); + return; + } + }; + drop(identity_secret); + let state = OmegaState::new(identity, config); + log!("Started"); log!(" .env"); if let Err(e) = initialize().await { @@ -96,7 +103,7 @@ async fn main() { .unwrap_or(443); tokio::select! { - result = omikron_connection::start(port, OmegaState::new()) => { + result = omikron_connection::start(port, state) => { if let Err(e) = result { log_err!(0, PrintType::General, "Server error: {:?}", e); } diff --git a/src/models/mod.rs b/src/models/mod.rs index 291729d..da21449 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -11,7 +11,8 @@ fn serialize_public_key( where S: serde::Serializer, { - serializer.serialize_str(&key.to_base64()) + let encoded = key.try_to_base64().map_err(serde::ser::Error::custom)?; + serializer.serialize_str(&encoded) } pub use ids::{IotaId, OmikronId, UserId}; diff --git a/src/server/api.rs b/src/server/api.rs index 23debe4..41e5d22 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -8,7 +8,7 @@ use crate::db::{ user_repo::{get_by_user_id, get_by_username}, }; use crate::error::{OmegaError, Result}; -use crate::load_keyring; +use crate::identity::OmegaIdentity; use crate::models::UserId; use crate::server::{ middleware, @@ -24,6 +24,7 @@ use bytes::Bytes; use http::{Method, StatusCode}; use mtp::webserver::{HttpRequest, HttpResponse, RouteParams}; use std::collections::BTreeMap; +use std::sync::Arc; fn error_body(error: &OmegaError) -> String { json(&StatusResponse { @@ -35,11 +36,11 @@ fn error_body(error: &OmegaError) -> String { }) } -fn user_response(user: crate::models::User) -> UserResponse { - UserResponse { +fn user_response(user: crate::models::User) -> Result { + Ok(UserResponse { status: "success", username: user.username, - public_key: user.public_key.to_base64(), + public_key: user.public_key.try_to_base64()?, user_id: user.id.0, iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, @@ -50,10 +51,10 @@ fn user_response(user: crate::models::User) -> UserResponse { avatar: user .avatar .map(|value| base64::engine::general_purpose::STANDARD.encode(value)), - } + }) } -async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { +async fn route(path_parts: &[&str], identity: &OmegaIdentity) -> Result<(StatusCode, String)> { match path_parts { ["api", "get", "omikron"] => { let connection = get_random_omikron() @@ -69,7 +70,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&OmikronResponse { status: "success", id, - public_key: omikron.public_key.to_base64(), + public_key: omikron.public_key.try_to_base64()?, ip_address: omikron.ip_address, port: omikron.port, }), @@ -105,7 +106,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&OmikronResponse { status: "success", id: omikron.id.0, - public_key: omikron.public_key.to_base64(), + public_key: omikron.public_key.try_to_base64()?, ip_address: omikron.ip_address, port: omikron.port, }), @@ -120,12 +121,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { .map(|(omikron_id, iotas)| { let iotas = iotas .into_iter() - .map(|(iota_id, users)| { - ( - iota_id.to_string(), - users.into_iter().map(i64::from).collect(), - ) - }) + .map(|(iota_id, users)| (iota_id.to_string(), users.into_iter().collect())) .collect(); (omikron_id.to_string(), iotas) }) @@ -146,7 +142,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&IotaResponse { status: "success", iota_id: iota.id.0, - public_key: iota.public_key.to_base64(), + public_key: iota.public_key.try_to_base64()?, }), )) } @@ -158,7 +154,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&UsernameResponse { status: "success", username: user.username, - public_key: user.public_key.to_base64(), + public_key: user.public_key.try_to_base64()?, user_id: user.id.0, iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, @@ -168,7 +164,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } ["api", "get", "public_key"] => { let public_key = base64::engine::general_purpose::STANDARD - .encode(load_keyring().public_key_bundle().as_bytes()); + .encode(identity.public_key_bundle().try_as_bytes()?); Ok(( StatusCode::OK, json(&PublicKeyResponse { @@ -180,7 +176,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { ["api", "get", "user", id] => { let id = parse_positive_id(id)?; let user = get_by_user_id(UserId::from(id)).await?; - Ok((StatusCode::OK, json(&user_response(user)))) + Ok((StatusCode::OK, json(&user_response(user)?))) } _ => Ok(( StatusCode::INTERNAL_SERVER_ERROR, @@ -189,13 +185,17 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } } -pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpResponse { +pub async fn handle( + request: HttpRequest, + response: HttpResponse, + identity: Arc, +) -> HttpResponse { let method = request.method; let path = request.uri.path().to_string(); if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { return response .status(StatusCode::TOO_MANY_REQUESTS) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .body(json(&StatusResponse { status: "error_rate_limited", })); @@ -203,7 +203,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons if method == Method::OPTIONS { return response .status(StatusCode::OK) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .header("access-control-allow-methods", "GET, POST, OPTIONS") .header("access-control-allow-headers", "*"); } @@ -213,7 +213,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons return match std::fs::read(file_path) { Ok(bytes) => response .status(StatusCode::OK) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .header("content-type", "application/zip") .header( "content-disposition", @@ -222,7 +222,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons .body(Bytes::from(bytes)), Err(_) => response .status(StatusCode::NOT_FOUND) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .body(json(&StatusResponse { status: "error_not_found", })), @@ -237,12 +237,12 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons .status(StatusCode::TEMPORARY_REDIRECT) .header("location", &location); } - let (status, body) = route(&path_parts) + let (status, body) = route(&path_parts, &identity) .await .unwrap_or_else(|error| (error.status_code(), error_body(&error))); response .status(status) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .header("access-control-allow-headers", "*") .header("access-control-allow-methods", "GET, POST, OPTIONS") .body(body) @@ -252,6 +252,7 @@ pub async fn handle_pattern( request: HttpRequest, response: HttpResponse, _params: RouteParams, + identity: Arc, ) -> HttpResponse { - handle(request, response).await + handle(request, response, identity).await } diff --git a/src/server/middleware.rs b/src/server/middleware.rs index 5f373ec..b279298 100644 --- a/src/server/middleware.rs +++ b/src/server/middleware.rs @@ -1,14 +1,25 @@ use dashmap::DashMap; use once_cell::sync::Lazy; use std::net::IpAddr; -use std::{collections::VecDeque, time::Instant}; +use std::{collections::VecDeque, sync::OnceLock, time::Instant}; use tokio::time::interval; static REQUESTS: Lazy>> = Lazy::new(DashMap::new); -static CONFIG: Lazy = - Lazy::new(crate::config::RateLimitConfig::from_env); +static CONFIG: OnceLock = OnceLock::new(); const MAX_TRACKED_CLIENT_BUCKETS: usize = 100_000; +pub(crate) fn initialize_config( + config: crate::config::RateLimitConfig, +) -> Result<(), crate::config::RateLimitConfig> { + CONFIG.set(config) +} + +fn config() -> &'static crate::config::RateLimitConfig { + static FALLBACK: Lazy = + Lazy::new(crate::config::RateLimitConfig::from_env_or_default); + CONFIG.get().unwrap_or(&FALLBACK) +} + pub fn allow(remote_addr: IpAddr, path: &str) -> bool { let key = if path.contains("register") { "registration" @@ -23,15 +34,15 @@ pub fn allow(remote_addr: IpAddr, path: &str) -> bool { } } let limit = if key == "registration" { - CONFIG.registration_requests + config().registration_requests } else { - CONFIG.general_requests + config().general_requests }; let now = Instant::now(); let mut entries = REQUESTS.entry(map_key).or_default(); while entries .front() - .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + .is_some_and(|time| now.duration_since(*time) >= config().window) { entries.pop_front(); } @@ -44,7 +55,7 @@ pub fn allow(remote_addr: IpAddr, path: &str) -> bool { pub fn spawn_cleanup_task() -> tokio::task::JoinHandle<()> { tokio::spawn(async { - let mut ticker = interval(CONFIG.window); + let mut ticker = interval(config().window); loop { ticker.tick().await; cleanup_expired(); @@ -57,7 +68,7 @@ fn cleanup_expired() { REQUESTS.retain(|_, entries| { while entries .front() - .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + .is_some_and(|time| now.duration_since(*time) >= config().window) { entries.pop_front(); } diff --git a/src/server/mod.rs b/src/server/mod.rs index 2d01c5b..64df9e9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,6 @@ pub mod api; pub mod index; pub mod middleware; -pub mod server; pub mod short_link; pub mod validation; +pub mod web; diff --git a/src/server/server.rs b/src/server/server.rs deleted file mode 100644 index 9ac4f06..0000000 --- a/src/server/server.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::server::{api, index::index_handler}; -use mtp::webserver::WebServerConfig; - -pub fn build_web_config() -> Result { - WebServerConfig::new() - .route("/api/download/iota_frontend", api::handle)? - .route("/api/get/omikron", api::handle)? - .route("/api/get/connections", api::handle)? - .route("/api/get/public_key", api::handle)? - .route_pattern("/api/get/omikron/{id}", api::handle_pattern)? - .route_pattern("/api/get/iota/{id}", api::handle_pattern)? - .route_pattern("/api/get/id/{username}", api::handle_pattern)? - .route_pattern("/api/get/user/{id}", api::handle_pattern)? - .route_pattern("/direct/{short}", api::handle_pattern)? - .fallback(|_request, response| async move { index_handler(response) }) -} diff --git a/src/server/short_link.rs b/src/server/short_link.rs index dd24ebf..6089957 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -34,7 +34,7 @@ pub async fn get_short_link(short: &str) -> Result { short }; let frag = short.replace(key, ""); - let normalized = normalize_short(&key); + let normalized = normalize_short(key); let target = short_link_repo::get(&normalized) .await diff --git a/src/server/web.rs b/src/server/web.rs new file mode 100644 index 0000000..5975888 --- /dev/null +++ b/src/server/web.rs @@ -0,0 +1,47 @@ +use crate::identity::OmegaIdentity; +use crate::server::{api, index::index_handler}; +use mtp::webserver::{HttpRequest, HttpResponse, RouteParams, WebServerConfig}; +use std::{future::Future, pin::Pin, sync::Arc}; + +type RouteFuture = Pin + Send>>; + +fn api_handler( + identity: Arc, +) -> impl Fn(HttpRequest, HttpResponse) -> RouteFuture + Send + Sync + 'static { + move |request, response| Box::pin(api::handle(request, response, identity.clone())) +} + +fn api_pattern_handler( + identity: Arc, +) -> impl Fn(HttpRequest, HttpResponse, RouteParams) -> RouteFuture + Send + Sync + 'static { + move |request, response, params| { + Box::pin(api::handle_pattern( + request, + response, + params, + identity.clone(), + )) + } +} + +pub fn build_web_config( + identity: Arc, +) -> Result { + WebServerConfig::new() + .route("/api/download/iota_frontend", api_handler(identity.clone()))? + .route("/api/get/omikron", api_handler(identity.clone()))? + .route("/api/get/connections", api_handler(identity.clone()))? + .route("/api/get/public_key", api_handler(identity.clone()))? + .route_pattern( + "/api/get/omikron/{id}", + api_pattern_handler(identity.clone()), + )? + .route_pattern("/api/get/iota/{id}", api_pattern_handler(identity.clone()))? + .route_pattern( + "/api/get/id/{username}", + api_pattern_handler(identity.clone()), + )? + .route_pattern("/api/get/user/{id}", api_pattern_handler(identity.clone()))? + .route_pattern("/direct/{short}", api_pattern_handler(identity))? + .fallback(|_request, response| async move { index_handler(response) }) +} diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 104e316..00be3c9 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -12,11 +12,7 @@ pub enum UserStatus { iota_online, iota_borked, } -#[allow(unused)] impl UserStatus { - pub fn to_string(&self) -> String { - format!("{:?}", self) - } pub fn from_client_preference(s: &str) -> Option { match s { "user_online" => Some(Self::user_online), @@ -38,11 +34,18 @@ impl UserStatus { /// Parse a value received from a client or persisted as an account /// preference. Derived connectivity and diagnostic states are never valid /// preferences. + #[allow(dead_code)] pub fn from_str(s: &str) -> Option { Self::from_client_preference(s) } } +impl std::fmt::Display for UserStatus { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "{self:?}") + } +} + #[cfg(test)] mod tests { use super::UserStatus; diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index be3e87c..cac8ab6 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -275,10 +275,6 @@ impl PresenceTracker { Ok(()) } - pub fn check_index_consistency(&self) -> Result<(), String> { - Self::check_indices(&self.routes.read().unwrap()) - } - pub fn set_preference(&self, user_id: i64, status: UserStatus) { self.preferences.write().unwrap().insert(user_id, status); } @@ -335,14 +331,6 @@ impl PresenceTracker { }) } - pub fn track_iota(&self, iota_id: i64, omikron_id: i64) { - self.connect_iota(iota_id, omikron_id); - } - - pub fn untrack_iota(&self, iota_id: i64, omikron_id: i64) -> bool { - self.disconnect_iota(iota_id, omikron_id) - } - pub fn track_session(&self, user_id: i64, session_id: i64, omikron_id: i64, iota_id: i64) { let mut routes = self.routes.write().unwrap(); Self::insert_session_locked( @@ -373,15 +361,6 @@ impl PresenceTracker { .is_some_and(|route| route.omikron_id == omikron_id) } - pub fn session_route(&self, user_id: i64, session_id: i64) -> Option { - self.routes - .read() - .unwrap() - .sessions - .get(&(user_id, session_id)) - .map(|route| route.clone()) - } - pub fn resolve_public_state(&self, user_id: i64, iota_id: i64) -> UserStatus { if !self.has_iota_route(iota_id) { return UserStatus::iota_offline; @@ -496,12 +475,6 @@ impl PresenceTracker { Self::debug_assert_indices(&routes); } - pub fn remove_subscription(&self, user_id: i64, session_id: i64) { - let mut routes = self.routes.write().unwrap(); - Self::remove_subscription_locked(&mut routes, (user_id, session_id)); - Self::debug_assert_indices(&routes); - } - pub fn subscribers(&self, target_user_id: i64) -> Vec { let mut subscribers = self .routes @@ -542,15 +515,6 @@ impl PresenceTracker { sessions } - pub fn replace_routes_owned_by( - &self, - omikron_id: i64, - iota_ids: &[i64], - sessions: &[(i64, i64, i64)], - ) { - self.replace_omikron_snapshot(omikron_id, iota_ids, sessions); - } - pub fn replace_omikron_snapshot( &self, omikron_id: i64, @@ -621,10 +585,6 @@ impl PresenceTracker { ) } - pub fn track_iota_connection(&self, iota_id: i64, omikron_id: i64, primary: bool) { - self.connect_iota_with_priority(iota_id, omikron_id, primary); - } - pub fn untrack_iota_connection(&self, iota_id: i64, omikron_id: i64) -> bool { let connections_empty = self.disconnect_iota(iota_id, omikron_id); self.remove_sessions_for_iota(iota_id, omikron_id); @@ -671,29 +631,6 @@ impl PresenceTracker { user_routes.into_iter().map(|(_, route)| route).collect() } - pub fn sessions_for_user(&self, user_id: i64) -> Vec<(i64, ClientSessionRoute)> { - let routes = self.routes.read().unwrap(); - let mut sessions = routes - .indices - .sessions_by_user - .get(&user_id) - .into_iter() - .flat_map(|session_ids| session_ids.iter()) - .filter_map(|session_id| { - routes - .sessions - .get(&(user_id, *session_id)) - .map(|route| (*session_id, route.clone())) - }) - .collect::>(); - sessions.sort_by_key(|(session_id, route)| (*session_id, route.omikron_id)); - sessions - } - - pub fn route_for_session(&self, user_id: i64, session_id: i64) -> Option { - self.session_route(user_id, session_id) - } - pub fn user_route(&self, user_id: i64) -> Option { self.routes_for_user(user_id).into_iter().next() } @@ -745,10 +682,6 @@ impl PresenceTracker { } result } - - pub fn remove_omikron_and_offline_users(&self, omikron_id: i64) -> RemovedOmikronState { - self.remove_omikron(omikron_id) - } } #[cfg(test)] @@ -756,6 +689,10 @@ mod tests { use super::{ClientSessionRoute, PresenceTracker}; use crate::sql::connection_status::UserStatus; + fn check_index_consistency(tracker: &PresenceTracker) -> Result<(), String> { + PresenceTracker::check_indices(&tracker.routes.read().unwrap()) + } + #[test] fn effective_state_is_derived_from_routes_and_preference() { let tracker = PresenceTracker::default(); @@ -765,7 +702,7 @@ mod tests { UserStatus::iota_offline ); - tracker.track_iota(11, 42); + tracker.connect_iota(11, 42); assert_eq!( tracker.resolve_public_state(7, 11), UserStatus::user_offline @@ -783,7 +720,7 @@ mod tests { #[test] fn session_must_match_assigned_iota_and_live_route() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); + tracker.connect_iota(11, 42); tracker.track_session(7, 3, 42, 12); assert!(!tracker.has_active_session_for_iota(7, 11)); assert_eq!( @@ -807,7 +744,7 @@ mod tests { #[test] fn resolver_covers_all_public_preference_states() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); + tracker.connect_iota(11, 42); tracker.track_session(7, 3, 42, 11); for (preference, expected) in [ @@ -826,14 +763,20 @@ mod tests { fn session_routes_replace_by_session_without_replacing_preferences() { let tracker = PresenceTracker::default(); tracker.set_preference(7, UserStatus::user_dnd); - tracker.track_iota(11, 42); - tracker.track_iota(12, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(12, 43); tracker.track_session(7, 3, 42, 11); tracker.track_session(7, 4, 43, 12); assert_eq!(tracker.preference(7), UserStatus::user_dnd); assert_eq!( - tracker.session_route(7, 3), + tracker + .routes + .read() + .unwrap() + .sessions + .get(&(7, 3)) + .cloned(), Some(ClientSessionRoute { omikron_id: 42, iota_id: 11, @@ -865,21 +808,21 @@ mod tests { #[test] fn losing_one_iota_route_keeps_iota_online() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); - tracker.track_iota(11, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); assert!(tracker.has_iota_route(11)); - assert!(!tracker.untrack_iota(11, 42)); + assert!(!tracker.disconnect_iota(11, 42)); assert!(tracker.has_iota_route(11)); - assert!(tracker.untrack_iota(11, 43)); + assert!(tracker.disconnect_iota(11, 43)); assert!(!tracker.has_iota_route(11)); } #[test] fn iota_connection_routes_track_primary_and_replacement() { let tracker = PresenceTracker::default(); - tracker.track_iota_connection(11, 42, true); - tracker.track_iota_connection(11, 43, false); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); assert_eq!(tracker.iota_connections(11), Some(vec![42, 43])); assert_eq!(tracker.primary_iota_route(11), Some(42)); @@ -961,8 +904,8 @@ mod tests { #[test] fn active_session_can_use_one_of_several_live_iota_routes() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); - tracker.track_iota(11, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); tracker.track_session(7, 3, 43, 11); assert!(tracker.has_active_session_for_iota(7, 11)); @@ -973,13 +916,13 @@ mod tests { #[test] fn route_snapshot_replaces_only_one_omikron() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); - tracker.track_iota(12, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(12, 43); tracker.track_session(7, 3, 42, 11); tracker.track_session(8, 4, 43, 12); tracker.replace_subscription(7, 3, 42, vec![20]); - tracker.replace_routes_owned_by(42, &[12], &[(9, 5, 12)]); + tracker.replace_omikron_snapshot(42, &[12], &[(9, 5, 12)]); assert!(!tracker.owns_session(7, 3, 42)); assert!(tracker.owns_session(8, 4, 43)); @@ -992,8 +935,8 @@ mod tests { fn omikron_cleanup_preserves_other_routes_and_preferences() { let tracker = PresenceTracker::default(); tracker.set_preference(7, UserStatus::user_dnd); - tracker.track_iota(11, 42); - tracker.track_iota(11, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); tracker.track_session(7, 3, 42, 11); tracker.track_session(7, 4, 43, 11); tracker.replace_subscription(7, 3, 42, vec![20]); @@ -1036,14 +979,14 @@ mod tests { .cloned(), Some([20, 21].into_iter().collect()) ); - assert!(tracker.check_index_consistency().is_ok()); + assert!(check_index_consistency(&tracker).is_ok()); tracker.replace_subscription(7, 3, 42, vec![22]); assert!(tracker.subscribers(20).is_empty()); assert!(tracker.subscribers(21).is_empty()); assert_eq!(tracker.subscribers(22).len(), 1); tracker.remove_session(7, 3, 42); - assert!(tracker.check_index_consistency().is_ok()); + assert!(check_index_consistency(&tracker).is_ok()); assert!( tracker .routes @@ -1076,7 +1019,7 @@ mod tests { assert_eq!(removed.iota_ids, vec![11]); assert_eq!(removed.session_user_ids, vec![7]); assert_eq!(removed.subscriber_sessions, vec![(7, 3)]); - assert!(tracker.check_index_consistency().is_ok()); + assert!(check_index_consistency(&tracker).is_ok()); } #[test] @@ -1093,6 +1036,6 @@ mod tests { .unwrap() .clear(); - assert!(tracker.check_index_consistency().is_err()); + assert!(check_index_consistency(&tracker).is_err()); } } diff --git a/src/state.rs b/src/state.rs index 02c899b..215f1b3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,10 @@ -use crate::sql::user_online_tracker::PresenceTracker; +use crate::{ + config::OmegaConfig, identity::OmegaIdentity, sql::user_online_tracker::PresenceTracker, +}; use dashmap::DashMap; use std::sync::Arc; use std::time::{Duration, Instant}; +use tokio::sync::Semaphore; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum AccountChallengeOperation { @@ -11,28 +14,50 @@ pub enum AccountChallengeOperation { #[derive(Clone, Debug)] pub struct AccountChallenge { - pub operation: AccountChallengeOperation, - pub user_id: i64, - pub requester_iota_id: i64, pub nonce: u64, pub created_at: Instant, } pub struct OmegaState { pub presence: Arc, + pub identity: Arc, + pub(crate) config: Arc, + pub(crate) global_handler_limit: Arc, + pub(crate) omikron_handler_concurrency: usize, challenges: DashMap<(AccountChallengeOperation, i64, i64), AccountChallenge>, } -impl Default for OmegaState { - fn default() -> Self { +impl OmegaState { + fn with_handler_limits(identity: OmegaIdentity, config: OmegaConfig) -> Self { + let omikron_handler_concurrency = config.dispatch.omikron_handler_concurrency; + let global_handler_concurrency = config.dispatch.global_handler_concurrency; Self { presence: Arc::new(PresenceTracker::default()), + identity: Arc::new(identity), + config: Arc::new(config), + global_handler_limit: Arc::new(Semaphore::new(global_handler_concurrency)), + omikron_handler_concurrency, challenges: DashMap::new(), } } -} -impl OmegaState { + pub fn new(identity: OmegaIdentity, config: OmegaConfig) -> Arc { + Arc::new(Self::with_handler_limits(identity, config)) + } + + #[cfg(test)] + fn test_state() -> Arc { + use mtp::crypto::Keyring; + + let mut config = OmegaConfig::default(); + config.dispatch.omikron_handler_concurrency = 4; + config.dispatch.global_handler_concurrency = 8; + Arc::new(Self::with_handler_limits( + OmegaIdentity::from_keyring(Keyring::generate()), + config, + )) + } + pub fn issue_challenge( &self, operation: AccountChallengeOperation, @@ -43,9 +68,6 @@ impl OmegaState { self.challenges.insert( (operation, user_id, requester_iota_id), AccountChallenge { - operation, - user_id, - requester_iota_id, nonce, created_at: Instant::now(), }, @@ -68,12 +90,6 @@ impl OmegaState { } } -impl OmegaState { - pub fn new() -> Arc { - Arc::new(Self::default()) - } -} - #[cfg(test)] mod tests { use super::OmegaState; @@ -81,10 +97,10 @@ mod tests { #[test] fn state_instances_have_independent_presence_trackers() { - let first = OmegaState::new(); - let second = OmegaState::new(); + let first = OmegaState::test_state(); + let second = OmegaState::test_state(); - first.presence.track_iota_connection(11, 42, true); + first.presence.connect_iota(11, 42); assert!(first.presence.has_iota_route(11)); assert!(!second.presence.has_iota_route(11)); @@ -94,10 +110,10 @@ mod tests { #[test] fn two_session_private_and_public_presence_flow_is_authoritative() { - let state = OmegaState::new(); + let state = OmegaState::test_state(); state.presence.set_preference(7, UserStatus::user_online); state.presence.set_preference(8, UserStatus::user_online); - state.presence.track_iota_connection(11, 42, true); + state.presence.connect_iota(11, 42); state.presence.track_session(7, 100, 42, 11); state.presence.track_session(7, 101, 42, 11); state.presence.replace_subscription(7, 100, 42, vec![8]); diff --git a/src/transport/connection.rs b/src/transport/connection.rs index 6cada3e..66d9eb3 100644 --- a/src/transport/connection.rs +++ b/src/transport/connection.rs @@ -2,24 +2,94 @@ pub(crate) use super::omikron_connection::{OmikronConnection, OmikronResult}; use mtp::codec::{CommunicationValue, DataValue}; -pub(crate) trait MtpValueCompat { - fn get_id(&self) -> u32; - fn get_sender(&self) -> u64; - fn get_receiver(&self) -> u64; +pub(crate) trait RequiredMtpFields { + /// Require a nonzero request correlation ID. `None` and `Some(0)` are + /// distinct MTP wire states, but both are invalid for Omega requests. + fn require_id(&self) -> OmikronResult; + /// Require a nonzero authenticated peer identity. + fn require_sender(&self) -> OmikronResult; + /// Require a nonzero application routing target. + fn require_receiver(&self) -> OmikronResult; + fn require_sender_i64(&self) -> OmikronResult; + fn require_receiver_i64(&self) -> OmikronResult; } -impl MtpValueCompat for CommunicationValue { - fn get_id(&self) -> u32 { - self.id().unwrap_or_default() +impl RequiredMtpFields for CommunicationValue { + fn require_id(&self) -> OmikronResult { + self.id().filter(|id| *id != 0).ok_or_else(|| { + crate::OmegaError::Validation("MTP message is missing request id".into()) + }) } - fn get_sender(&self) -> u64 { - self.sender().unwrap_or_default() + fn require_sender(&self) -> OmikronResult { + self.sender() + .filter(|sender| *sender != 0) + .ok_or_else(|| crate::OmegaError::Validation("MTP message is missing sender".into())) } - fn get_receiver(&self) -> u64 { - self.receiver().unwrap_or_default() + fn require_receiver(&self) -> OmikronResult { + self.receiver() + .filter(|receiver| *receiver != 0) + .ok_or_else(|| crate::OmegaError::Validation("MTP message is missing receiver".into())) } + + fn require_sender_i64(&self) -> OmikronResult { + positive_i64(self.require_sender()?, "sender") + } + + fn require_receiver_i64(&self) -> OmikronResult { + positive_i64(self.require_receiver()?, "receiver") + } +} + +pub(crate) fn positive_i64(value: impl TryInto, field: &str) -> OmikronResult { + let value = value + .try_into() + .map_err(|_| crate::OmegaError::Validation(format!("invalid {field}")))?; + let value = i64::try_from(value) + .map_err(|_| crate::OmegaError::Validation(format!("invalid {field}")))?; + if value <= 0 { + return Err(crate::OmegaError::Validation(format!("invalid {field}"))); + } + Ok(value) +} + +pub(crate) fn validate_dispatch_fields(value: &CommunicationValue) -> OmikronResult<()> { + let Some(message_type) = value.get_comm_type_enum() else { + return Err(crate::OmegaError::Validation( + "MTP message has an unknown communication type".into(), + )); + }; + + if !matches!( + message_type, + mtp::codec::CommunicationType::ClientChanged + | mtp::codec::CommunicationType::PushNotification + ) { + value.require_id()?; + } + + if matches!( + message_type, + mtp::codec::CommunicationType::GetUserData + | mtp::codec::CommunicationType::ChangeUserData + | mtp::codec::CommunicationType::ChangeIotaData + | mtp::codec::CommunicationType::DeleteUser + | mtp::codec::CommunicationType::AttachUserBegin + | mtp::codec::CommunicationType::AttachUserComplete + | mtp::codec::CommunicationType::DeleteUserCredentialBegin + | mtp::codec::CommunicationType::DeleteUserCredentialComplete + | mtp::codec::CommunicationType::EraseHostedUserDataAck + | mtp::codec::CommunicationType::ReleaseUserFromIota + | mtp::codec::CommunicationType::DeleteIota + | mtp::codec::CommunicationType::GetNotifications + | mtp::codec::CommunicationType::ReadNotification + | mtp::codec::CommunicationType::StateSubscribe + ) { + value.require_sender()?; + } + + Ok(()) } pub(crate) trait OptionalDataValueCompat { @@ -46,3 +116,65 @@ impl OptionalDataValueCompat for Option<&DataValue> { self.and_then(|value| value.as_bytes()) } } + +#[cfg(test)] +mod tests { + use super::{RequiredMtpFields, validate_dispatch_fields}; + use mtp::codec::{CommunicationType, CommunicationValue}; + + #[test] + fn missing_request_id_is_rejected_without_a_zero_fallback() { + let value = CommunicationValue::new(CommunicationType::GetUserData).without_id(); + assert!(value.require_id().is_err()); + assert!(validate_dispatch_fields(&value).is_err()); + } + + #[test] + fn zero_routing_values_are_rejected_by_omega_contract() { + let value = CommunicationValue::new(CommunicationType::GetUserData) + .with_id(0) + .with_sender(0) + .with_receiver(0); + + assert!(value.require_id().is_err()); + assert!(value.require_sender().is_err()); + assert!(value.require_receiver().is_err()); + assert!(validate_dispatch_fields(&value).is_err()); + } + + #[test] + fn security_sensitive_messages_require_sender() { + for message_type in [ + CommunicationType::GetUserData, + CommunicationType::ChangeUserData, + CommunicationType::ChangeIotaData, + CommunicationType::DeleteUser, + CommunicationType::AttachUserBegin, + CommunicationType::AttachUserComplete, + CommunicationType::DeleteUserCredentialBegin, + CommunicationType::DeleteUserCredentialComplete, + CommunicationType::EraseHostedUserDataAck, + CommunicationType::ReleaseUserFromIota, + CommunicationType::DeleteIota, + CommunicationType::GetNotifications, + CommunicationType::ReadNotification, + CommunicationType::StateSubscribe, + ] { + let value = CommunicationValue::new(message_type).with_id(1); + assert!(value.require_sender().is_err()); + assert!(validate_dispatch_fields(&value).is_err()); + } + } + + #[test] + fn optional_client_changed_id_remains_optional() { + let value = CommunicationValue::new(CommunicationType::ClientChanged).without_id(); + assert!(validate_dispatch_fields(&value).is_ok()); + } + + #[test] + fn push_notification_uses_its_logical_sender_field() { + let value = CommunicationValue::new(CommunicationType::PushNotification); + assert!(validate_dispatch_fields(&value).is_ok()); + } +} diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs index 0374f78..ae7215f 100644 --- a/src/transport/handlers/account.rs +++ b/src/transport/handlers/account.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::{iota_repo, user_repo}, @@ -22,24 +22,30 @@ async fn delete( Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), }; - connection.send(&response.with_id(value.get_id())).await + connection + .send(&response.with_id(value.require_id()?)) + .await } pub async fn user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let user_id = UserId::from(value.get_sender() as i64); + value.require_id()?; + value.require_sender_i64()?; + let user_id = UserId::from(value.require_sender_i64()?); complete_delete(connection, value, user_id).await } pub async fn iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; delete( connection, value.clone(), - iota_repo::delete_iota(IotaId::from(value.get_sender() as i64)), + iota_repo::delete_iota(IotaId::from(value.require_sender_i64()?)), ) .await } @@ -48,6 +54,8 @@ pub async fn release_from_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -55,18 +63,21 @@ pub async fn release_from_iota( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let requester = IotaId::from(value.get_sender() as i64); + let requester = IotaId::from(value.require_sender_i64()?); let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; }; if user.iota_id != Some(requester) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await; } let previous_iota = user.iota_id; @@ -76,14 +87,17 @@ pub async fn release_from_iota( crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Err(error) => { connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await @@ -104,6 +118,8 @@ pub async fn attach_begin( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -111,7 +127,7 @@ pub async fn attach_begin( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; if user_repo::get_by_user_id(UserId::from(user_id)) @@ -119,10 +135,10 @@ pub async fn attach_begin( .is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; } - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let nonce = connection .state() @@ -130,7 +146,7 @@ pub async fn attach_begin( connection .send( &CommunicationValue::new(CommunicationType::AttachUserChallenge) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), ) @@ -141,6 +157,8 @@ pub async fn attach_complete( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -148,24 +166,30 @@ pub async fn attach_complete( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let Some(nonce) = value .get_data(DataType::ServerNonce) .as_signed_number() .and_then(|v| u64::try_from(v).ok()) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; let signature = value.get_data(DataType::Signature).as_bytes(); let pq_signature = value.get_data(DataType::PqSignature).as_bytes(); let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; if !connection.state().consume_challenge( @@ -175,12 +199,15 @@ pub async fn attach_complete( nonce, ) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; } let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; }; let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce); @@ -188,7 +215,10 @@ pub async fn attach_complete( || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await; } let previous_iota = user.iota_id; @@ -199,12 +229,15 @@ pub async fn attach_complete( } crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Err(_) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .send_error_response(value.require_id()?, CommunicationType::ErrorInternal) .await } } @@ -225,7 +258,7 @@ async fn complete_delete( connection .send( &CommunicationValue::new(CommunicationType::Success) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default( DataType::CleanupPending, DataValue::Bool(cleanup_pending), @@ -235,14 +268,14 @@ async fn complete_delete( } Err(crate::error::OmegaError::NotFound) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await } Err(error) => { connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await @@ -254,6 +287,8 @@ pub async fn delete_credential_begin( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -261,7 +296,7 @@ pub async fn delete_credential_begin( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; if user_repo::get_by_user_id(UserId::from(user_id)) @@ -269,10 +304,10 @@ pub async fn delete_credential_begin( .is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; } - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let nonce = connection .state() @@ -280,7 +315,7 @@ pub async fn delete_credential_begin( connection .send( &CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), ) @@ -291,6 +326,8 @@ pub async fn delete_credential_complete( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -298,17 +335,20 @@ pub async fn delete_credential_complete( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let Some(nonce) = value .get_data(DataType::ServerNonce) .as_signed_number() .and_then(|v| u64::try_from(v).ok()) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; let (Some(signature), Some(pq_signature)) = ( @@ -316,7 +356,10 @@ pub async fn delete_credential_complete( value.get_data(DataType::PqSignature).as_bytes(), ) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; if !connection.state().consume_challenge( @@ -326,12 +369,15 @@ pub async fn delete_credential_complete( nonce, ) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; } let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; }; let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce); @@ -339,7 +385,10 @@ pub async fn delete_credential_complete( || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await; } complete_delete(connection, value, user.id).await @@ -349,6 +398,8 @@ pub async fn erase_hosted_user_data_ack( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -356,45 +407,31 @@ pub async fn erase_hosted_user_data_ack( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let iota_id = IotaId::from(value.get_sender() as i64); + let iota_id = IotaId::from(value.require_sender_i64()?); match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await { Ok(true) => { connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Ok(false) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await } Err(_) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .send_error_response(value.require_id()?, CommunicationType::ErrorInternal) .await } } } - -/// New lifecycle operation names are intentionally fail-closed until their -/// proof and durable-erasure handlers are enabled. This explicit dispatch -/// prevents either a bare Iota request or the legacy DeleteUser path from -/// acquiring account-deletion authority during a staged rollout. -pub async fn lifecycle_unavailable( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorNotAuthenticated) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str("user lifecycle proof handler is not enabled".into()), - ), - ) - .await -} diff --git a/src/transport/handlers/links.rs b/src/transport/handlers/links.rs index ad45c05..10df9e4 100644 --- a/src/transport/handlers/links.rs +++ b/src/transport/handlers/links.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::server::short_link::add_short_link; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; @@ -17,7 +17,7 @@ pub async fn shorten( .await .map_err(|_| crate::error::OmegaError::Transport("short link error".to_string()))?; let response = CommunicationValue::new(CommunicationType::ShortenLink) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::Link, DataValue::Str(short)); connection.send(&response).await } diff --git a/src/transport/handlers/notifications.rs b/src/transport/handlers/notifications.rs index 9405bf0..59883c5 100644 --- a/src/transport/handlers/notifications.rs +++ b/src/transport/handlers/notifications.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{db::notification_repo, log, models::UserId}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; @@ -10,35 +10,36 @@ pub async fn get( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let notifications = - match notification_repo::get_notifications(UserId::from(value.get_sender() as i64)).await { - Ok(items) => items - .into_iter() - .map(|item| { - let tm = TypeMap::latest(); - let Some(sender) = DataType::SenderId.try_to_id(&tm) else { - return DataValue::Container(Vec::new()); - }; - let Some(amount) = DataType::Amount.try_to_id(&tm) else { - return DataValue::Container(Vec::new()); - }; - DataValue::Container(vec![ - (sender, DataValue::SignedNumber(item.sender_id.0.into())), - (amount, DataValue::SignedNumber(item.amount.into())), - ]) - }) - .collect(), - Err(error) => { - log!( - crate::util::logger::PrintType::General, - "SQL get_notifications error: {}", - error - ); - Vec::new() - } - }; + let request_id = value.require_id()?; + let sender = value.require_sender_i64()?; + let notifications = match notification_repo::get_notifications(UserId::from(sender)).await { + Ok(items) => items + .into_iter() + .map(|item| { + let tm = TypeMap::latest(); + let Some(sender) = DataType::SenderId.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + let Some(amount) = DataType::Amount.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + DataValue::Container(vec![ + (sender, DataValue::SignedNumber(item.sender_id.0.into())), + (amount, DataValue::SignedNumber(item.amount.into())), + ]) + }) + .collect(), + Err(error) => { + log!( + crate::util::logger::PrintType::General, + "SQL get_notifications error: {}", + error + ); + Vec::new() + } + }; let response = CommunicationValue::new(CommunicationType::GetNotifications) - .with_id(value.get_id()) + .with_id(request_id) .add_typed_default(DataType::Notifications, DataValue::Array(notifications)); connection.send(&response).await } @@ -47,19 +48,17 @@ pub async fn read( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let receiver = match value.get_sender() { - sender if sender > 0 => sender as i64, - _ => match value.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; + let request_id = value.require_id()?; + let receiver = value.require_sender_i64()?; let Some(other) = value .get_data(DataType::SenderId) .as_number() - .map(|id| id as i64) + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { - return Ok(()); + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; }; if let Err(error) = notification_repo::read_notification(UserId::from(receiver), UserId::from(other)).await @@ -71,7 +70,7 @@ pub async fn read( ); } else { let response = - CommunicationValue::new(CommunicationType::ReadNotification).with_id(value.get_id()); + CommunicationValue::new(CommunicationType::ReadNotification).with_id(request_id); let _ = connection.send(&response).await; let sync = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver as u64) @@ -85,18 +84,54 @@ pub async fn push( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let receiver = match value.get_receiver() { - receiver if receiver > 0 => receiver as i64, - _ => match value.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, + let request_id = value.id().filter(|id| *id != 0); + let receiver = value.require_receiver_i64().ok().or_else(|| { + value + .get_data(DataType::ReceiverId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + }); + let Some(receiver) = receiver else { + if let Some(request_id) = request_id { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + return Ok(()); }; - let sender = value + let Some(sender) = value .get_data(DataType::SenderId) .as_number() - .map(|id| id as i64) - .unwrap_or(value.get_sender() as i64); + .and_then(|sender| i64::try_from(sender).ok()) + .filter(|sender| *sender > 0) + else { + if let Some(request_id) = request_id { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + return Ok(()); + }; + let source_omikron = connection + .clone() + .get_omikron_id() + .await + .ok_or(crate::OmegaError::NotConnected)?; + if !connection + .state() + .presence + .routes_for_user(sender) + .iter() + .any(|route| route.omikron_id == source_omikron) + { + if let Some(request_id) = request_id { + return connection + .send_error_response(request_id, CommunicationType::ErrorNotAuthenticated) + .await; + } + return Ok(()); + } if let Err(error) = notification_repo::add_notification(UserId::from(receiver), UserId::from(sender)).await { @@ -106,9 +141,11 @@ pub async fn push( error ); } else { - let response = - CommunicationValue::new(CommunicationType::PushNotification).with_id(value.get_id()); - let _ = connection.send(&response).await; + if let Some(request_id) = request_id { + let response = + CommunicationValue::new(CommunicationType::PushNotification).with_id(request_id); + let _ = connection.send(&response).await; + } let push = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver as u64) .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender.into())); diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index 5c631bf..a958a92 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::user_repo, log_in, models::IotaId, sql::connection_status::UserStatus, state::OmegaState, @@ -11,7 +11,10 @@ use std::{ }; fn parse_subscription(value: &CommunicationValue) -> Result<(i64, i64, Vec), &'static str> { - let user_id = i64::try_from(value.get_sender()) + let Some(sender) = value.sender() else { + return Err("user_id"); + }; + let user_id = i64::try_from(sender) .ok() .filter(|id| *id > 0) .ok_or("user_id")?; @@ -51,13 +54,11 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa users .iter() .map(|user| { - ( - user.id.0, - state.presence.resolve_public_state( - user.id.0, - user.iota_id.map(|id| id.0).unwrap_or_default(), - ), - ) + let status = user + .iota_id + .map(|iota_id| state.presence.resolve_public_state(user.id.0, iota_id.0)) + .unwrap_or(UserStatus::user_offline); + (user.id.0, status) }) .collect() } @@ -70,9 +71,10 @@ fn changed_states( let mut changes = users .iter() .filter_map(|user| { - let after = state - .presence - .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()); + let after = user + .iota_id + .map(|iota_id| state.presence.resolve_public_state(user.id.0, iota_id.0)) + .unwrap_or(UserStatus::user_offline); (before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after)) }) .collect::>(); @@ -96,21 +98,6 @@ fn state_notification( .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) } -fn private_state_notification( - user_id: i64, - session_id: i64, - user_state: &UserStatus, -) -> CommunicationValue { - CommunicationValue::new(CommunicationType::ClientChanged) - .with_receiver(user_id as u64) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id.into()), - ) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) - .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) -} - async fn publish_state_changes(state: &OmegaState, changes: &[(i64, UserStatus)]) { let mut grouped = BTreeMap::>::new(); for (user_id, user_state) in changes { @@ -143,28 +130,6 @@ async fn publish_changed_states( publish_state_changes(state, &changed_states(state, before, users)).await; } -async fn publish_private_state(state: &OmegaState, user_id: i64, user_state: &UserStatus) { - let mut grouped = BTreeMap::>::new(); - for (session_id, route) in state.presence.sessions_for_user(user_id) { - grouped - .entry(route.omikron_id) - .or_default() - .push(private_state_notification(user_id, session_id, user_state)); - } - for (omikron_id, notifications) in grouped { - if let Err(error) = - crate::transport::omikron_manager::send_state_batch(omikron_id, notifications).await - { - log_in!( - crate::util::logger::PrintType::General, - "Failed to deliver private presence state batch to Omikron {}: {}", - omikron_id, - error - ); - } - } -} - pub async fn state_subscribe( state: Arc, connection: Arc, @@ -175,13 +140,13 @@ pub async fn state_subscribe( Ok(subscription) => subscription, Err("user_id") => { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorNoUserId) .await; } Err(detail) => { return connection .send_error_response_with_detail( - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, detail, ) @@ -190,14 +155,14 @@ pub async fn state_subscribe( }; if !state.presence.owns_session(user_id, session_id, omikron_id) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .send_error_response(value.require_id()?, CommunicationType::ErrorNoIota) .await; } state .presence .replace_subscription(user_id, session_id, omikron_id, user_ids); connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -242,7 +207,7 @@ pub async fn user_connected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let Some(session_id) = value @@ -252,7 +217,7 @@ pub async fn user_connected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let Some(iota_id) = value @@ -262,25 +227,25 @@ pub async fn user_connected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let user = match user_repo::get_by_user_id(user_id.into()).await { Ok(user) => user, Err(crate::error::OmegaError::NotFound) => { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; } - Err(error) => return Err(error.into()), + Err(error) => return Err(error), }; let preferences = match user_repo::get_presence_preferences(&[user_id]).await { Ok(preferences) => preferences, - Err(error) => return Err(error.into()), + Err(error) => return Err(error), }; if user.iota_id.map(|id| id.0) != Some(iota_id) || !state.presence.has_iota_route(iota_id) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .send_error_response(value.require_id()?, CommunicationType::ErrorNoIota) .await; } apply_preferences(&state, preferences); @@ -291,7 +256,7 @@ pub async fn user_connected( .track_session(user_id, session_id, omikron_id, iota_id); publish_changed_states(&state, &before, &users).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -309,7 +274,7 @@ pub async fn user_disconnected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let Some(session_id) = value @@ -319,7 +284,7 @@ pub async fn user_disconnected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; if let Ok(user) = user_repo::get_by_user_id(user_id.into()).await { @@ -337,137 +302,7 @@ pub async fn user_disconnected( .remove_session(user_id, session_id, omikron_id); } connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) - .await -} - -pub async fn set_user_state( - state: Arc, - connection: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - let Some(user_id) = i64::try_from(value.get_sender()).ok().filter(|id| *id > 0) else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) - .await; - }; - if let Some(requested_user) = value.get_data(DataType::UserId) { - let Some(requested_user_id) = requested_user - .as_number() - .and_then(|id| i64::try_from(id).ok()) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "user_id", - ) - .await; - }; - if requested_user_id != user_id { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "user_id", - ) - .await; - } - } - let Some(iota_id) = value - .get_data(DataType::IotaId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "iota_id", - ) - .await; - }; - let Some(requested_state) = value - .get_data(DataType::UserState) - .as_str() - .and_then(UserStatus::from_client_preference) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "user_state", - ) - .await; - }; - if !state.presence.has_iota_route(iota_id) { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) - .await; - } - let Some(session_id) = value - .get_data(DataType::SessionId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "session_id", - ) - .await; - }; - let Some(route) = state.presence.session_route(user_id, session_id) else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) - .await; - }; - if route.omikron_id != omikron_id || route.iota_id != iota_id { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) - .await; - } - if !state.presence.has_active_session_for_iota(user_id, iota_id) { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) - .await; - } - let previous_preference = state.presence.preference(user_id); - let previous_state = state.presence.resolve_public_state(user_id, iota_id); - if let Err(error) = - user_repo::change_presence_preference(user_id.into(), requested_state.to_string()).await - { - log_in!( - crate::util::logger::PrintType::General, - "Failed to persist presence preference: {}", - error - ); - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInternal) - .await; - } - state - .presence - .set_preference(user_id, requested_state.clone()); - let new_state = state.presence.resolve_public_state(user_id, iota_id); - if requested_state != previous_preference { - publish_private_state(&state, user_id, &requested_state).await; - } - if requested_state != previous_preference && new_state != previous_state { - publish_state_changes(&state, &[(user_id, new_state)]).await; - } - connection - .send( - &CommunicationValue::new(CommunicationType::Success) - .with_id(value.get_id()) - .add_typed_default( - DataType::UserState, - DataValue::Str(requested_state.to_string()), - ), - ) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -481,10 +316,11 @@ pub async fn iota_connected( let Some(iota_id) = value .get_data(DataType::IotaId) .as_number() - .map(|id| id as i64) + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; @@ -503,7 +339,7 @@ pub async fn iota_connected( crate::transport::omikron_manager::deliver_pending_erasures(iota_id).await; publish_changed_states(&state, &before, &users).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -517,10 +353,11 @@ pub async fn iota_disconnected( let Some(iota_id) = value .get_data(DataType::IotaId) .as_number() - .map(|id| id as i64) + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; @@ -530,7 +367,7 @@ pub async fn iota_disconnected( state.presence.untrack_iota_connection(iota_id, omikron_id); publish_changed_states(&state, &before, &users).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -540,7 +377,7 @@ pub async fn sync_status( value: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { - let request_id = value.get_id(); + let request_id = value.require_id()?; let Some(DataValue::Array(iota_values)) = value.get_data(DataType::IotaIds) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs index 8799893..2c2501a 100644 --- a/src/transport/handlers/register.rs +++ b/src/transport/handlers/register.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::{iota_repo, user_repo}, @@ -22,13 +22,13 @@ pub async fn get_register( .filter(|id| user_repo::valid_protocol_id(*id)); let Some(iota_id) = iota_id else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let (register_id, registration_token) = - user_repo::allocate_registration(IotaId::from(iota_id), value.get_id()).await?; + user_repo::allocate_registration(IotaId::from(iota_id), value.require_id()?).await?; let response = CommunicationValue::new(CommunicationType::GetRegister) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default( DataType::UserId, DataValue::SignedNumber(register_id.0.into()), @@ -47,7 +47,7 @@ pub async fn complete_iota( .and_then(|key| PublicKeyBundle::from_base64(key).ok()); let Some(public_key) = public_key else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; match iota_repo::create_new_iota(public_key).await { @@ -55,7 +55,7 @@ pub async fn complete_iota( connection .send( &CommunicationValue::new(CommunicationType::CompleteRegisterIota) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.0.into())), ) .await @@ -64,7 +64,7 @@ pub async fn complete_iota( connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await @@ -108,7 +108,7 @@ pub async fn complete_user( }) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; // Omikron supplies the authenticated Iota ID in the payload. The lease @@ -120,7 +120,7 @@ pub async fn complete_user( .filter(|id| user_repo::valid_protocol_id(*id)); let Some(iota_id) = iota_id else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; @@ -136,14 +136,17 @@ pub async fn complete_user( { Ok(()) => { connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Err(error) => { connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index e1fc12d..0b46254 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::db::user_repo; use mtp::{ @@ -34,7 +34,7 @@ pub async fn get( let Some(DataValue::Array(ids)) = value.get_data(DataType::UserIds) else { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, None, ) @@ -47,7 +47,7 @@ pub async fn get( if session_id.is_none() && !legacy_peer { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, None, ) @@ -60,7 +60,7 @@ pub async fn get( let DataValue::SignedNumber(id) = id else { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, session_id, ) @@ -69,7 +69,7 @@ pub async fn get( let Ok(user_id) = i64::try_from(*id) else { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, session_id, ) @@ -78,7 +78,7 @@ pub async fn get( if user_id <= 0 { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, session_id, ) @@ -95,7 +95,7 @@ pub async fn get( Err(_) => { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInternal, session_id, ) @@ -110,9 +110,10 @@ pub async fn get( missing_user_ids.push(user_id); continue; }; - let status = state - .presence - .resolve_public_state(user_id, user.iota_id.map(|id| id.0).unwrap_or_default()) + let status = user + .iota_id + .map(|iota_id| state.presence.resolve_public_state(user_id, iota_id.0)) + .unwrap_or(crate::sql::connection_status::UserStatus::user_offline) .to_string(); let mut map = Vec::new(); if let Some(kind) = DataType::UserId.try_to_id(&tm) { @@ -124,7 +125,7 @@ pub async fn get( states.push(DataValue::Container(map)); } let response = CommunicationValue::new(CommunicationType::GetStates) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::UserStates, DataValue::Array(states)); let response = if let Some(session_id) = session_id { response.add_typed_default(DataType::SessionId, DataValue::SignedNumber(session_id)) diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index 1c248fa..e6b676a 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::{iota_repo, user_repo}, @@ -29,11 +29,16 @@ pub async fn get_user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let request_id = value.require_id()?; + let sender = value.require_sender_i64()?; let state = connection.state(); - let user = if let Some(id) = value.get_data(DataType::UserId).as_number() { - user_repo::get_by_user_id(UserId::from(id as i64)) - .await - .ok() + let user = if let Some(id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + user_repo::get_by_user_id(UserId::from(id)).await.ok() } else if let Some(name) = value.get_data(DataType::Username).as_str() { user_repo::get_by_username(name).await.ok() } else { @@ -41,7 +46,7 @@ pub async fn get_user( }; let Some(user) = user else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(request_id, CommunicationType::ErrorNotFound) .await; }; let id = user.id.0; @@ -52,11 +57,11 @@ pub async fn get_user( .filter(|name| !name.is_empty()) .unwrap_or_else(|| username.clone()); let mut response = CommunicationValue::new(CommunicationType::GetUserData) - .with_id(value.get_id()) + .with_id(request_id) .add_typed_default(DataType::Username, DataValue::Str(username)) .add_typed_default( DataType::PublicKey, - DataValue::Str(user.public_key.to_base64()), + DataValue::Str(user.public_key.try_to_base64()?), ) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::Display, DataValue::Str(display)) @@ -79,7 +84,7 @@ pub async fn get_user( response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar))); } let route = state.presence.user_route(id); - let private_request = value.get_sender() as i64 == id; + let private_request = sender == id; let resolved_status = if private_request { if !state .presence @@ -125,13 +130,21 @@ pub async fn get_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let found = if let Some(id) = value.get_data(DataType::IotaId).as_number() { - iota_repo::get_iota_by_id(IotaId::from(id as i64)) + let request_id = value.require_id()?; + let found = if let Some(id) = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + iota_repo::get_iota_by_id(IotaId::from(id)) .await .ok() .map(|iota| (iota.id.0, iota.public_key, None, None)) } else if let Some(id) = value.get_data(DataType::UserId).as_number() { - if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await { + if let Some(id) = i64::try_from(id).ok().filter(|id| *id > 0) + && let Ok(user) = user_repo::get_by_user_id(UserId::from(id)).await + { match user.iota_id { Some(iota_id) => iota_repo::get_iota_by_id(iota_id) .await @@ -163,12 +176,12 @@ pub async fn get_iota( }; let Some((id, key, user_id, username)) = found else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(request_id, CommunicationType::ErrorNotFound) .await; }; let mut response = CommunicationValue::new(CommunicationType::GetIotaData) - .with_id(value.get_id()) - .add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64())) + .with_id(request_id) + .add_typed_default(DataType::PublicKey, DataValue::Str(key.try_to_base64()?)) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::OmikronConnections, connections(&connection, id)); if let Some(user_id) = user_id { @@ -185,7 +198,8 @@ async fn update_user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let id = UserId::from(value.get_sender() as i64); + let request_id = value.require_id()?; + let id = UserId::from(value.require_sender_i64()?); let mut error = None; if let Some(name) = value.get_data(DataType::Username).as_str() { error = user_repo::change_username(id, name.to_owned()) @@ -193,56 +207,55 @@ async fn update_user( .err() .map(|e| e.to_string()); } - if error.is_none() { - if let Some(name) = value.get_data(DataType::Display).as_str() { - error = user_repo::change_display_name(id, name.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(name) = value.get_data(DataType::Display).as_str() + { + error = user_repo::change_display_name(id, name.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(avatar) = value.get_data(DataType::Avatar).as_str() { - error = user_repo::change_avatar(id, avatar.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(avatar) = value.get_data(DataType::Avatar).as_str() + { + error = user_repo::change_avatar(id, avatar.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(about) = value.get_data(DataType::About).as_str() { - error = user_repo::change_about(id, about.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(about) = value.get_data(DataType::About).as_str() + { + error = user_repo::change_about(id, about.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(status) = value.get_data(DataType::Status).as_str() { - error = user_repo::change_status(id, status.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(status) = value.get_data(DataType::Status).as_str() + { + error = user_repo::change_status(id, status.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(key) = value + if error.is_none() + && let Some(key) = value .get_data(DataType::PublicKey) .as_str() .and_then(|key| PublicKeyBundle::from_base64(key).ok()) - { - error = user_repo::change_keys(id, key) - .await - .err() - .map(|e| e.to_string()); - } + { + error = user_repo::change_keys(id, key) + .await + .err() + .map(|e| e.to_string()); } let response = match error { None => CommunicationValue::new(CommunicationType::Success), Some(error) => CommunicationValue::new(CommunicationType::ErrorInternal) .add_typed_default(DataType::ErrorType, DataValue::Str(error)), }; - connection.send(&response.with_id(value.get_id())).await + connection.send(&response.with_id(request_id)).await } pub async fn change_user( @@ -256,34 +269,35 @@ pub async fn change_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let request_id = value.require_id()?; let reset_data = value.get_data(DataType::ResetToken); let Some(reset) = reset_data.as_str() else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let new_token_data = value.get_data(DataType::NewToken); let Some(new_token) = new_token_data.as_str() else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; - let user_id = UserId::from(value.get_sender() as i64); + let user_id = UserId::from(value.require_sender_i64()?); let user = match user_repo::get_by_user_id(user_id).await { Ok(user) => user, Err(_) => { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(request_id, CommunicationType::ErrorNotFound) .await; } }; if user.token != reset { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response(request_id, CommunicationType::ErrorInvalidChallenge) .await; } let result = - match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))) + match user_repo::change_iota_id(user_id, Some(IotaId::from(value.require_sender_i64()?))) .await { Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, @@ -294,5 +308,5 @@ pub async fn change_iota( Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), }; - connection.send(&response.with_id(value.get_id())).await + connection.send(&response.with_id(request_id)).await } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 82b9ece..83a9df7 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,12 +1,13 @@ use super::capabilities::{OmegaCapabilities, PeerCapabilities}; use crate::models::OmikronId; use crate::{ - load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server, + log, log_cv_in, log_cv_out, log_err, log_in, server, state::OmegaState, + transport::connection::{RequiredMtpFields, validate_dispatch_fields}, transport::omikron_manager, util::{file_util::load_file_vec, logger::PrintType}, }; -use dashmap::DashMap; +use dashmap::{DashMap, mapref::entry::Entry}; use mtp::{ codec::{CommunicationType, CommunicationValue}, crypto::PublicKeyBundle, @@ -16,19 +17,21 @@ use mtp::{ use std::{ net::{IpAddr, Ipv4Addr}, sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, + Arc, Mutex as StdMutex, + atomic::{AtomicU32, AtomicUsize, Ordering}, }, time::{Duration, Instant}, }; use tokio::{ - sync::{Mutex, mpsc}, + sync::{Mutex, Semaphore, mpsc, oneshot}, time::interval, }; +use tokio_util::{sync::CancellationToken, task::TaskTracker}; const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0); +static NEXT_CORRELATION_ID: AtomicU32 = AtomicU32::new(1); static ACTIVE_CONNECTIONS_BY_IP: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(DashMap::new); @@ -47,16 +50,27 @@ impl Drop for ConnectionLimitGuard { pub type OmikronResult = crate::error::Result; pub struct WaitingTask { - pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub sender: oneshot::Sender, pub inserted_at: Instant, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum DispatchClass { + Concurrent, + Ordered, +} + pub struct OmikronConnection { id: u64, state: Arc, sender: Mutex>, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, + handler_tasks: TaskTracker, + handler_cancel: CancellationToken, + handler_limit: Arc, + ordered_sender: mpsc::Sender, + ordered_receiver: StdMutex>>, peer_capabilities: PeerCapabilities, } impl Drop for OmikronConnection { @@ -64,6 +78,9 @@ impl Drop for OmikronConnection { if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); } + self.handler_cancel.cancel(); + self.handler_tasks.close(); + self.waiting_tasks.clear(); } } @@ -75,17 +92,24 @@ impl OmikronConnection { state: Arc, authenticated: bool, ) -> Option> { - if !authenticated { + if !authenticated || id == 0 || i64::try_from(id).is_err() { return None; } let peer_capabilities = PeerCapabilities::from_identification_description(description).ok()?; + let handler_concurrency = state.omikron_handler_concurrency; + let (ordered_sender, ordered_receiver) = mpsc::channel(handler_concurrency); Some(Arc::new(Self { id, state, sender: Mutex::new(Some(sender)), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), + handler_tasks: TaskTracker::new(), + handler_cancel: CancellationToken::new(), + handler_limit: Arc::new(Semaphore::new(handler_concurrency)), + ordered_sender, + ordered_receiver: StdMutex::new(Some(ordered_receiver)), peer_capabilities, })) } @@ -94,6 +118,115 @@ impl OmikronConnection { &self.peer_capabilities } + fn track_handler(&self, task: F) + where + F: std::future::Future + Send + 'static, + { + let handle = self.handler_tasks.spawn(task); + let id = self.id as i64; + tokio::spawn(async move { + if let Err(error) = handle.await { + log_err!( + id, + PrintType::Omega, + "Omikron handler task failed: {}", + error + ); + } + }); + } + + fn start_ordered_worker(self: &Arc) { + let Some(mut receiver) = self.ordered_receiver.lock().unwrap().take() else { + return; + }; + let connection = self.clone(); + let cancellation = self.handler_cancel.clone(); + self.track_handler(async move { + loop { + let value = tokio::select! { + _ = cancellation.cancelled() => break, + value = receiver.recv() => match value { + Some(value) => value, + None => break, + }, + }; + let global_permit = tokio::select! { + _ = cancellation.cancelled() => break, + permit = connection.state.global_handler_limit.clone().acquire_owned() => { + match permit { + Ok(permit) => permit, + Err(_) => break, + } + } + }; + let permit = tokio::select! { + _ = cancellation.cancelled() => break, + permit = connection.handler_limit.clone().acquire_owned() => { + match permit { + Ok(permit) => permit, + Err(_) => break, + } + } + }; + let result = tokio::select! { + _ = cancellation.cancelled() => break, + result = connection.clone().dispatch(value) => result, + }; + drop(permit); + drop(global_permit); + if let Err(error) = result { + log_err!( + connection.id as i64, + PrintType::Omega, + "Error processing ordered Omikron message: {}", + error + ); + } + } + }); + } + + async fn stop_handlers(&self) { + self.handler_cancel.cancel(); + self.handler_tasks.close(); + let _ = tokio::time::timeout(Duration::from_secs(1), self.handler_tasks.wait()).await; + self.waiting_tasks.clear(); + } + + fn correlation_response(value: &CommunicationValue) -> bool { + matches!( + value.get_comm_type_enum(), + Some(CommunicationType::Success) + | Some(CommunicationType::ErrorProtocol) + | Some(CommunicationType::ErrorAnonymous) + | Some(CommunicationType::ErrorInternal) + | Some(CommunicationType::ErrorInvalidData) + | Some(CommunicationType::ErrorInvalidUserId) + | Some(CommunicationType::ErrorInvalidOmikronId) + | Some(CommunicationType::ErrorNotFound) + | Some(CommunicationType::ErrorNotAuthenticated) + | Some(CommunicationType::ErrorNoIota) + | Some(CommunicationType::ErrorInvalidChallenge) + | Some(CommunicationType::ErrorInvalidSecret) + | Some(CommunicationType::ErrorInvalidPrivateKey) + | Some(CommunicationType::ErrorInvalidPublicKey) + | Some(CommunicationType::ErrorNoUserId) + | Some(CommunicationType::ErrorNoCallId) + | Some(CommunicationType::ErrorInvalidCallId) + ) + } + + fn next_correlation_id(&self) -> Option { + for _ in 0..u32::MAX { + let id = NEXT_CORRELATION_ID.fetch_add(1, Ordering::Relaxed); + if id != 0 && !self.waiting_tasks.contains_key(&id) { + return Some(id); + } + } + None + } + pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, @@ -117,12 +250,16 @@ impl OmikronConnection { self.clone().cleanup().await; return; } - let cleanup_conn = self.clone(); + self.start_ordered_worker(); + let cleanup_conn = Arc::downgrade(&self); *self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); loop { ticker.tick().await; - cleanup_conn + let Some(connection) = cleanup_conn.upgrade() else { + break; + }; + connection .waiting_tasks .retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE); } @@ -164,16 +301,81 @@ impl OmikronConnection { async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { log_cv_in!(PrintType::Omikron, &value); - if value.is_type(CommunicationType::Relay) { - return self.dispatch(value).await; - } - if let Some(message_id) = value.id() + if Self::correlation_response(&value) + && let Some(message_id) = value.id() && let Some((_, task)) = self.waiting_tasks.remove(&message_id) { - let _ = (task.task)(self.clone(), value); + let _ = task.sender.send(value); return Ok(()); } - self.dispatch(value).await + + if !value.is_type(CommunicationType::Relay) { + validate_dispatch_fields(&value)?; + } + + let dispatch_class = Self::dispatch_class(&value); + if dispatch_class == DispatchClass::Ordered { + tokio::select! { + _ = self.handler_cancel.cancelled() => { + return Err(crate::error::OmegaError::NotConnected); + } + result = self.ordered_sender.send(value) => { + result.map_err(|_| crate::error::OmegaError::NotConnected)?; + } + } + return Ok(()); + } + + let global_permit = tokio::select! { + _ = self.handler_cancel.cancelled() => { + return Err(crate::error::OmegaError::NotConnected); + } + permit = self.state.global_handler_limit.clone().acquire_owned() => { + permit.map_err(|_| crate::error::OmegaError::NotConnected)? + } + }; + let permit = tokio::select! { + _ = self.handler_cancel.cancelled() => { + drop(global_permit); + return Err(crate::error::OmegaError::NotConnected); + } + permit = self.handler_limit.clone().acquire_owned() => { + permit.map_err(|_| crate::error::OmegaError::NotConnected)? + } + }; + let connection = self.clone(); + let cancellation = self.handler_cancel.clone(); + self.track_handler(async move { + tokio::select! { + _ = cancellation.cancelled() => {} + result = connection.clone().dispatch(value) => { + if let Err(error) = result { + log_err!( + connection.id as i64, + PrintType::Omega, + "Error processing Omikron message: {}", + error + ); + } + } + } + drop(permit); + drop(global_permit); + }); + Ok(()) + } + + fn dispatch_class(value: &CommunicationValue) -> DispatchClass { + match value.get_comm_type_enum() { + Some(CommunicationType::UserConnected) + | Some(CommunicationType::UserDisconnected) + | Some(CommunicationType::IotaConnected) + | Some(CommunicationType::IotaDisconnected) + | Some(CommunicationType::SyncClientIotaStatus) + | Some(CommunicationType::StateSubscribe) + | Some(CommunicationType::ClientChanged) => DispatchClass::Ordered, + _ => DispatchClass::Concurrent, + } } async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { @@ -341,33 +543,39 @@ impl OmikronConnection { value: &CommunicationValue, timeout: Duration, ) -> OmikronResult { - let (tx, mut rx) = mpsc::channel(1); - let message_id = value.id().unwrap_or_default(); - let task_tx = tx.clone(); - self.waiting_tasks.insert( - message_id, - WaitingTask { - task: Box::new(move |_, response| { - let task_tx = task_tx.clone(); - tokio::spawn(async move { - let _ = task_tx.send(response).await; - }); - true - }), - inserted_at: Instant::now(), - }, - ); + value.require_id()?; + let (tx, rx) = oneshot::channel(); + let message_id = self.next_correlation_id().ok_or_else(|| { + crate::OmegaError::Transport("no relay correlation id available".into()) + })?; + match self.waiting_tasks.entry(message_id) { + Entry::Vacant(entry) => { + entry.insert(WaitingTask { + sender: tx, + inserted_at: Instant::now(), + }); + } + Entry::Occupied(_) => { + return Err(crate::OmegaError::Transport( + "relay correlation id collision".into(), + )); + } + } + let outbound = value.clone().with_id(message_id); - if let Err(error) = self.clone().send(value).await { + if let Err(error) = self.clone().send(&outbound).await { self.waiting_tasks.remove(&message_id); return Err(error); } - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response)) => Ok(response), - Ok(None) => Err(crate::error::OmegaError::Transport( - "Relay response channel closed".into(), - )), + match tokio::time::timeout(timeout, rx).await { + Ok(Ok(response)) => Ok(response), + Ok(Err(_)) => { + self.waiting_tasks.remove(&message_id); + Err(crate::error::OmegaError::Transport( + "Relay response channel closed".into(), + )) + } Err(_) => { self.waiting_tasks.remove(&message_id); Err(crate::error::OmegaError::Transport( @@ -411,6 +619,7 @@ impl OmikronConnection { } } async fn cleanup(self: Arc) { + self.stop_handlers().await; if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); if omikron_manager::remove_omikron(self.id as i64, &self).await { @@ -441,7 +650,8 @@ pub async fn get_by_omikron_id( description: Option, ) -> Option { PeerCapabilities::from_identification_description(description.as_deref()).ok()?; - crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64)) + let omikron_id = i64::try_from(omikron_id).ok().filter(|id| *id > 0)?; + crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id)) .await .ok() .map(|omikron| omikron.public_key) @@ -453,7 +663,7 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { pub async fn start(port: u16, state: Arc) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "cert.pem")?; let key_pem = load_file_vec("certs", "key.pem")?; - let web_config = server::server::build_web_config()? + let web_config = server::web::build_web_config(state.identity.clone())? .serve_tcp_https(true) .max_tcp_connections(256); let ip = IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)); @@ -478,7 +688,7 @@ pub async fn start(port: u16, state: Arc) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box config.transport_connections || peer_active.is_some_and(|count| count > config.transport_connections_per_ip) { - drop(ConnectionLimitGuard(peer_ip)); + drop(connection_limit_guard); log_err!( 0, PrintType::Omega, @@ -526,6 +737,7 @@ pub async fn start(port: u16, state: Arc) -> Result<(), Box) -> Result<(), Box>> = - Lazy::new(|| DashMap::new()); + Lazy::new(DashMap::new); pub async fn add_omikron(conn: Arc) { let id = match conn.clone().get_omikron_id().await { @@ -64,12 +64,11 @@ pub async fn get_all_connections() .map_err(|_| ())?; for user in users { for route in state.presence.routes_for_user(user.id.0) { - if let Some(iotas) = result.get_mut(&route.omikron_id) { - if let Some(iota_id) = user.iota_id - && let Some(users) = iotas.get_mut(&iota_id.0) - { - users.push(user.id.0); - } + if let Some(iotas) = result.get_mut(&route.omikron_id) + && let Some(iota_id) = user.iota_id + && let Some(users) = iotas.get_mut(&iota_id.0) + { + users.push(user.id.0); } } } @@ -97,10 +96,10 @@ pub async fn send_state_batch( pub async fn get_random_omikron() -> Result, ()> { let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); - if let Some(key) = keys.into_iter().choose(&mut rand::rng()) { - if let Some(connection) = get_connected_omikron(key) { - return Ok(connection); - } + if let Some(key) = keys.into_iter().choose(&mut rand::rng()) + && let Some(connection) = get_connected_omikron(key) + { + return Ok(connection); } Err(()) diff --git a/src/transport/relay_router.rs b/src/transport/relay_router.rs index 3d76705..0cbd1f2 100644 --- a/src/transport/relay_router.rs +++ b/src/transport/relay_router.rs @@ -21,14 +21,6 @@ pub enum RouteTarget { } impl RouteTarget { - pub fn wire_id(self) -> Option { - let (kind, id) = match self { - Self::User(id) => (USER_TARGET_KIND, id), - Self::Iota(id) => (IOTA_TARGET_KIND, id), - }; - (id > 0 && id <= TARGET_ID_MASK).then_some(kind | id) - } - pub fn from_wire_id(value: u64) -> Option { let id = value & TARGET_ID_MASK; if id == 0 || value & !(TARGET_KIND_MASK | TARGET_ID_MASK) != 0 { @@ -40,12 +32,6 @@ impl RouteTarget { _ => None, } } - - pub const fn id(self) -> u64 { - match self { - Self::User(id) | Self::Iota(id) => id, - } - } } #[derive(Debug, Error)] @@ -183,10 +169,11 @@ mod tests { use mtp::codec::DataValue; fn wire(target: RouteTarget) -> u64 { - let Some(value) = target.wire_id() else { - panic!("valid route target was rejected"); + let (kind, id) = match target { + RouteTarget::User(id) => (USER_TARGET_KIND, id), + RouteTarget::Iota(id) => (IOTA_TARGET_KIND, id), }; - value + kind | id } fn relay_frame() -> CommunicationValue { diff --git a/src/util/file_util.rs b/src/util/file_util.rs index 8630489..05b9018 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -1,293 +1,15 @@ use once_cell::sync::Lazy; -use std::fs::{self, File}; -use std::io::{self, BufReader, Read}; -use std::path::{Path, PathBuf}; -use tokio::io::AsyncWriteExt; -use uuid::Uuid; -use zip::ZipArchive; - -use crate::log; +use std::path::PathBuf; static WORKING_DIR: Lazy = Lazy::new(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); -pub fn delete_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file = dir.join(name); - if !file.exists() { - return false; - } - fs::remove_file(file).is_ok() -} - -#[allow(dead_code)] -pub fn delete_directory(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - delete_dir_recursive(&dir) -} - -#[allow(dead_code)] -fn delete_dir_recursive(directory: &Path) -> bool { - if !directory.exists() { - return false; - } - if let Err(e) = fs::remove_dir_all(directory) { - log!( - "[IMPORTANT] Couldn't delete directory {}: {}", - directory.display(), - e - ); - return false; - } - true -} - -#[allow(dead_code)] -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_buf(path: &str, name: &str) -> io::Result> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - // Ensure the directory exists, create if necessary - if !dir.exists() { - if let Err(_) = fs::create_dir_all(&dir) { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "Directory creation failed", - )); - } - } - - // Create the file if it doesn't exist - if !file_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "File creation failed", - )); - } - - // Open the file and return a BufReader for efficient reading - let file = File::open(&file_path)?; - Ok(BufReader::new(file)) -} -pub fn has_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - if !dir.exists() { - return false; - } - - if !file_path.exists() { - return false; - } - - true -} -pub fn has_dir(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - - if !dir.exists() { - return false; - } - - true -} - -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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return String::new(); - } - return String::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!("[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 load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { - let dir = Path::new(&get_directory()).join(path); + let dir = PathBuf::from(get_directory()).join(path); let file_path = dir.join(name); - std::fs::read(file_path) } -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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return; - } - } - - if let Err(e) = fs::write(&file_path, value) { - log!( - "[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 { WORKING_DIR.to_string_lossy().to_string() } - -pub fn working_dir() -> &'static Path { - &WORKING_DIR -} - -// Helper to download the zip file content to a file on disk -#[allow(dead_code)] -async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { - let response = reqwest::get(url).await?; - - // Check for successful response status - if !response.status().is_success() { - return Err(format!("Failed to download file: Status {}", response.status()).into()); - } - - let mut zip_file = tokio::fs::File::create(as_name).await?; - let body = response.bytes().await?; - zip_file.write_all(&body).await?; - - Ok(()) -} - -#[allow(dead_code, deprecated)] -fn extract_zip_contents_to_folder( - zip_path: &Path, - target_dir: &Path, -) -> Result<(), Box> { - let file = File::open(zip_path)?; - let mut archive = ZipArchive::new(file)?; - - let staging_dir = target_dir.with_extension("staging"); - - let _ = fs::remove_dir_all(&staging_dir); - fs::create_dir_all(&staging_dir)?; - - let mut first_item_name: Option = None; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - let entry_path = staging_dir.join(file.sanitized_name()); - - if i == 0 { - if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { - first_item_name = Some(file.sanitized_name()); - } - } - - if file.name().ends_with('/') { - fs::create_dir_all(&entry_path)?; - } else { - if let Some(parent) = entry_path.parent() { - fs::create_dir_all(parent)?; - } - let mut out_file = File::create(entry_path)?; - io::copy(&mut file, &mut out_file)?; - } - } - - if let Some(root_path) = first_item_name { - let root_dir = staging_dir.join(&root_path); - - if root_dir.is_dir() { - let root_contents_count = fs::read_dir(&staging_dir)?.count(); - - if root_contents_count == 1 - || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) - { - let _ = fs::remove_dir_all(target_dir); - fs::create_dir_all(target_dir)?; - - for entry in fs::read_dir(root_dir)? { - let entry = entry?; - let src = entry.path(); - let dest = target_dir.join(entry.file_name()); - - if let Err(_) = fs::rename(&src, &dest) { - if src.is_file() { - fs::copy(&src, &dest)?; - } else { - if entry.path().is_dir() { - fs::rename(&src, &dest)?; - } - } - } - } - - let _ = fs::remove_dir_all(&staging_dir); - return Ok(()); - } - } - } - - log!("Extracting directly (no single root folder detected)."); - let _ = fs::remove_dir_all(target_dir); - fs::rename(&staging_dir, target_dir)?; - - Ok(()) -} - -#[allow(dead_code)] -pub async fn download_and_extract_zip(url: &str, as_name: &str) { - let base_dir = PathBuf::from(get_directory()); - let zip_filename = format!("{}.zip", Uuid::new_v4()); - let zip_path = base_dir.join(&zip_filename); - let target_dir = base_dir.join(as_name); - - if let Err(e) = download_zip(url, &zip_path).await { - log!("Error downloading file: {}", e); - return; - } - - let zip_path_clone = zip_path.clone(); - let target_dir_clone = target_dir.clone(); - let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone); - if let Err(e) = extract_result { - log!("Panic during ZIP extraction: {}", e); - } - - if let Err(e) = tokio::fs::remove_file(&zip_path).await { - log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index 9bd211a..c9c9719 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -10,7 +10,6 @@ use std::{ use ansi_term::Color; use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version}; -use crate::transport::connection::MtpValueCompat; use crate::util::file_util::get_directory; static LOGGER: OnceLock> = OnceLock::new(); @@ -223,7 +222,7 @@ pub fn log_cv_internal( let formatted = format_cv(cv); log_internal( - Some(cv.get_sender() as i64), + cv.sender().and_then(|sender| i64::try_from(sender).ok()), print_type.unwrap_or(PrintType::General), prefix, false, @@ -234,14 +233,14 @@ pub fn log_cv_internal( pub fn format_cv(cv: &CommunicationValue) -> String { let mut parts = Vec::new(); - let sender = cv.get_sender(); - let receiver = cv.get_receiver(); + let sender = cv.sender().filter(|sender| *sender != 0); + let receiver = cv.receiver().filter(|receiver| *receiver != 0); - if sender > 0 && receiver > 0 { + if let (Some(sender), Some(receiver)) = (sender, receiver) { parts.push(format!("{} > {}", sender, receiver)); - } else if sender > 0 { + } else if let Some(sender) = sender { parts.push(format!("{}", sender)); - } else if receiver > 0 { + } else if let Some(receiver) = receiver { parts.push(format!("> {}", receiver)); } @@ -249,7 +248,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String { .get_comm_type_enum() .map(|kind| kind.to_string()) .unwrap_or_else(|| cv.get_type().to_string()); - parts.push(format!("{} (id={})", comm_type, cv.get_id())); + let id = cv + .id() + .map(|id| id.to_string()) + .unwrap_or_else(|| "missing".to_string()); + parts.push(format!("{} (id={})", comm_type, id)); let data = cv.data().unwrap_or(&[]); @@ -258,7 +261,7 @@ pub fn format_cv(cv: &CommunicationValue) -> String { Version(3, 0), ); - parts.push(format!("{}", formated_data)); + parts.push(formated_data.to_string()); parts.join(": ") } From b3441a8902d0f8fcdfd5888c3dccadcae8fb03fe Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 20 Aug 2026 17:05:37 +0200 Subject: [PATCH 218/220] [Updt] Mtp 0.3.0 --- Cargo.lock | 514 +----------------------- Cargo.toml | 7 +- README.md | 13 + mtp-type-maps | 2 +- src/config.rs | 96 ++++- src/db/iota_repo.rs | 10 +- src/db/user_repo.rs | 49 +-- src/error.rs | 25 ++ src/identity.rs | 314 +++++++++++++++ src/main.rs | 69 ++-- src/models/mod.rs | 3 +- src/server/api.rs | 53 +-- src/server/middleware.rs | 27 +- src/server/mod.rs | 2 +- src/server/server.rs | 16 - src/server/short_link.rs | 2 +- src/server/web.rs | 47 +++ src/sql/connection_status.rs | 11 +- src/sql/user_online_tracker.rs | 123 ++---- src/state.rs | 60 ++- src/transport/connection.rs | 154 ++++++- src/transport/handlers/account.rs | 161 +++++--- src/transport/handlers/links.rs | 4 +- src/transport/handlers/notifications.rs | 139 ++++--- src/transport/handlers/presence.rs | 239 ++--------- src/transport/handlers/register.rs | 25 +- src/transport/handlers/states.rs | 23 +- src/transport/handlers/user_data.rs | 134 +++--- src/transport/omikron_connection.rs | 344 ++++++++++++++-- src/transport/omikron_manager.rs | 21 +- src/transport/relay_router.rs | 21 +- src/util/file_util.rs | 282 +------------ src/util/logger.rs | 21 +- 33 files changed, 1480 insertions(+), 1531 deletions(-) create mode 100644 src/identity.rs delete mode 100644 src/server/server.rs create mode 100644 src/server/web.rs diff --git a/Cargo.lock b/Cargo.lock index e71cd5f..2a4b6e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "aead" version = "0.5.2" @@ -18,17 +12,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "aes" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" -dependencies = [ - "cipher 0.5.2", - "cpubits", - "cpufeatures 0.3.0", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -212,7 +195,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", - "zeroize", ] [[package]] @@ -233,15 +215,6 @@ version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" -[[package]] -name = "bzip2" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c" -dependencies = [ - "libbz2-rs-sys", -] - [[package]] name = "cc" version = "1.4.3" @@ -273,7 +246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", - "cipher 0.4.4", + "cipher", "cpufeatures 0.2.17", ] @@ -296,7 +269,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20 0.9.1", - "cipher 0.4.4", + "cipher", "poly1305", "zeroize", ] @@ -308,20 +281,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", - "inout 0.1.4", + "inout", "zeroize", ] -[[package]] -name = "cipher" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" -dependencies = [ - "crypto-common 0.2.2", - "inout 0.2.2", -] - [[package]] name = "cmake" version = "0.1.58" @@ -359,22 +322,6 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" -[[package]] -name = "constant_time_eq" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" - -[[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" version = "0.10.1" @@ -391,12 +338,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "cpubits" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" - [[package]] name = "cpufeatures" version = "0.2.17" @@ -430,15 +371,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - [[package]] name = "crossbeam-queue" version = "0.3.13" @@ -547,12 +479,6 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" -[[package]] -name = "deflate64" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2" - [[package]] name = "der" version = "0.7.10" @@ -617,7 +543,6 @@ dependencies = [ "const-oid 0.10.2", "crypto-common 0.2.2", "ctutils", - "zeroize", ] [[package]] @@ -683,15 +608,6 @@ dependencies = [ "serde", ] -[[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" @@ -765,16 +681,6 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "miniz_oxide", - "zlib-rs", -] - [[package]] name = "flume" version = "0.11.1" @@ -1199,22 +1105,6 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", ] [[package]] @@ -1223,23 +1113,12 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ - "base64 0.22.1", "bytes", - "futures-channel", - "futures-util", "http", "http-body", "hyper", - "ipnet", - "libc", - "percent-encoding", "pin-project-lite", - "socket2", - "system-configuration", "tokio", - "tower-service", - "tracing", - "windows-registry", ] [[package]] @@ -1365,21 +1244,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "inout" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" -dependencies = [ - "hybrid-array", -] - -[[package]] -name = "ipnet" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" - [[package]] name = "itoa" version = "1.0.18" @@ -1484,12 +1348,6 @@ dependencies = [ "spin", ] -[[package]] -name = "libbz2-rs-sys" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c" - [[package]] name = "libc" version = "0.2.189" @@ -1551,15 +1409,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "lzma-rust2" -version = "0.16.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b" -dependencies = [ - "sha2 0.11.0", -] - [[package]] name = "md-5" version = "0.10.6" @@ -1576,28 +1425,12 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - [[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", - "simd-adler32", -] - [[package]] name = "mio" version = "1.2.2" @@ -1665,7 +1498,7 @@ dependencies = [ [[package]] name = "mtp" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-client", "mtp-codec", @@ -1681,7 +1514,7 @@ dependencies = [ [[package]] name = "mtp-client" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-codec", "mtp-common", @@ -1694,7 +1527,7 @@ dependencies = [ [[package]] name = "mtp-codec" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "base64 0.23.1", "byteorder", @@ -1708,7 +1541,7 @@ dependencies = [ [[package]] name = "mtp-common" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "quinn", "rustls", @@ -1719,7 +1552,7 @@ dependencies = [ [[package]] name = "mtp-crypto" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "argon2", "base64 0.22.1", @@ -1742,7 +1575,7 @@ dependencies = [ [[package]] name = "mtp-files" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-crypto", "rand 0.10.2", @@ -1753,7 +1586,7 @@ dependencies = [ [[package]] name = "mtp-host" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-codec", "mtp-common", @@ -1769,7 +1602,7 @@ dependencies = [ [[package]] name = "mtp-transport" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "async-trait", "mtp-codec", @@ -1789,7 +1622,7 @@ dependencies = [ [[package]] name = "mtp-type-map" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "serde", "serde_yaml", @@ -1798,7 +1631,7 @@ dependencies = [ [[package]] name = "mtp-webserver" version = "0.3.0" -source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70" +source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "async-trait", "bytes", @@ -1922,17 +1755,15 @@ dependencies = [ "mtp", "once_cell", "rand 0.10.2", - "reqwest", "rustls", "serde", "serde_json", "sqlx", - "strum", - "strum_macros", "thiserror 2.0.20", "tokio", + "tokio-util", "uuid", - "zip", + "zeroize", ] [[package]] @@ -1993,16 +1824,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pbkdf2" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629" -dependencies = [ - "digest 0.11.3", - "hmac 0.13.0", -] - [[package]] name = "pem" version = "3.0.6" @@ -2118,12 +1939,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppmd-rust" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24" - [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2306,44 +2121,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "reqwest" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "ring" version = "0.17.14" @@ -2446,7 +2223,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "jni", "log", @@ -2522,7 +2299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", - "core-foundation 0.10.1", + "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", @@ -2623,17 +2400,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "sha1" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.3", -] - [[package]] name = "sha2" version = "0.10.9" @@ -2713,12 +2479,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - [[package]] name = "simd_cesu8" version = "1.2.0" @@ -2918,7 +2678,7 @@ dependencies = [ "rand 0.8.7", "rsa", "serde", - "sha1 0.10.7", + "sha1", "sha2 0.10.9", "smallvec", "sqlx-core", @@ -3006,24 +2766,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "subtle" version = "2.6.1" @@ -3052,15 +2794,6 @@ dependencies = [ "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" @@ -3072,27 +2805,6 @@ dependencies = [ "syn 2.0.119", ] -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "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 = "thiserror" version = "1.0.69" @@ -3140,7 +2852,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "js-sys", "num-conv", "powerfmt", "serde_core", @@ -3247,56 +2958,12 @@ dependencies = [ "bytes", "futures-core", "futures-sink", + "futures-util", "libc", "pin-project-lite", "tokio", ] -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[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.44" @@ -3329,18 +2996,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typed-path" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" - [[package]] name = "typenum" version = "1.20.1" @@ -3453,15 +3108,6 @@ dependencies = [ "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 = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -3487,16 +3133,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.127" @@ -3529,16 +3165,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "web-sys" -version = "0.3.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -3605,35 +3231,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -3982,81 +3579,8 @@ dependencies = [ "syn 3.0.3", ] -[[package]] -name = "zip" -version = "8.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" -dependencies = [ - "aes", - "bzip2", - "constant_time_eq", - "crc32fast", - "deflate64", - "flate2", - "getrandom 0.4.3", - "hmac 0.13.0", - "indexmap", - "lzma-rust2", - "memchr", - "pbkdf2", - "ppmd-rust", - "sha1 0.11.0", - "time", - "typed-path", - "zeroize", - "zopfli", - "zstd", -] - -[[package]] -name = "zlib-rs" -version = "0.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" - [[package]] name = "zmij" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" - -[[package]] -name = "zopfli" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" -dependencies = [ - "bumpalo", - "crc32fast", - "log", - "simd-adler32", -] - -[[package]] -name = "zstd" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" -dependencies = [ - "zstd-safe", -] - -[[package]] -name = "zstd-safe" -version = "7.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.16+zstd.1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/Cargo.toml b/Cargo.toml index 28e732a..395ad06 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition = "2024" mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [ "crypto", "files", - "raw", "web-server", ] } @@ -19,7 +18,6 @@ dotenv = "0.15.0" http = "1" once_cell = "1.21.4" rand = "0.10.2" -reqwest = "0.13.4" rustls = { version = "0.23.42", default-features = false, features = [ "std", "tls12", @@ -27,11 +25,10 @@ rustls = { version = "0.23.42", default-features = false, features = [ "prefer-post-quantum", ] } sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } -strum = "0.28.0" -strum_macros = "0.28.0" tokio = { version = "*", features = ["full"] } +tokio-util = { version = "0.7.19", features = ["rt"] } uuid = { version = "1.24.0", features = ["v4", "v7"] } -zip = "8.6.0" thiserror = "2.0.19" serde = { version = "1.0.229", features = ["derive"] } serde_json = "1.0.151" +zeroize = "1.9" diff --git a/README.md b/README.md index a1d4baf..2c05741 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,15 @@ # Omega The Omega is Tensamin's central Server. It maintains the centralized user Registry & manages Omikron useage. + +Omega requires the non-empty `OMEGA_IDENTITY_SECRET` environment variable at startup. On first start it creates the protected private identity at `./omega.mk` and the matching public bundle at `./omega.mpkb`. Existing identity files are loaded fail-closed, so a malformed keyring or mismatched public bundle stops startup. A missing public bundle is rebuilt from a valid private keyring without generating a new identity. + +## MTP routing contract + +Omega preserves the MTP distinction between an absent routing field and a +present zero value on the wire. For Omega request and response operations, +both forms are rejected: + +- request IDs are required and nonzero for correlation; +- authenticated sender and receiver IDs are required and nonzero; +- fields that are optional for a particular notification remain `Option` and + are not converted to zero. diff --git a/mtp-type-maps b/mtp-type-maps index 486541b..a297dcc 160000 --- a/mtp-type-maps +++ b/mtp-type-maps @@ -1 +1 @@ -Subproject commit 486541b9483356ff49ff3ec7016f87d3ecbeaa0e +Subproject commit a297dcce60bc6e84696c6a16f5fd510beb2ca643 diff --git a/src/config.rs b/src/config.rs index 66f5382..d3c7b29 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,5 @@ use std::{env, time::Duration}; +use thiserror::Error; #[derive(Clone, Debug)] pub struct RateLimitConfig { @@ -9,6 +10,26 @@ pub struct RateLimitConfig { pub transport_connections_per_ip: usize, } +#[derive(Clone, Debug)] +pub struct DispatchConfig { + pub omikron_handler_concurrency: usize, + pub global_handler_concurrency: usize, +} + +#[derive(Clone, Debug, Default)] +pub struct OmegaConfig { + pub rate_limits: RateLimitConfig, + pub dispatch: DispatchConfig, +} + +#[derive(Debug, Error)] +pub enum ConfigError { + #[error("invalid configuration value for {name}: {value}")] + InvalidValue { name: String, value: String }, + #[error("configuration value for {name} is not valid Unicode")] + InvalidUnicode { name: String }, +} + pub const fn cors_origin() -> &'static str { "*" } @@ -26,40 +47,75 @@ impl Default for RateLimitConfig { } impl RateLimitConfig { - pub fn from_env() -> Self { + pub fn from_env() -> Result { let defaults = Self::default(); - Self { - window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window), - general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests), + Ok(Self { + window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window)?, + general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests)?, registration_requests: env_usize( "RATE_LIMIT_REGISTRATION_REQUESTS", defaults.registration_requests, - ), + )?, transport_connections: env_usize( "RATE_LIMIT_TRANSPORT_CONNECTIONS", defaults.transport_connections, - ), + )?, transport_connections_per_ip: env_usize( "RATE_LIMIT_TRANSPORT_CONNECTIONS_PER_IP", defaults.transport_connections_per_ip, - ), + )?, + }) + } + + pub fn from_env_or_default() -> Self { + Self::from_env().unwrap_or_default() + } +} + +impl Default for DispatchConfig { + fn default() -> Self { + Self { + omikron_handler_concurrency: 32, + global_handler_concurrency: 256, } } } -fn env_usize(name: &str, fallback: usize) -> usize { - env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .unwrap_or(fallback) +impl OmegaConfig { + pub fn from_env() -> Result { + let rate_limits = RateLimitConfig::from_env()?; + let defaults = DispatchConfig::default(); + Ok(Self { + rate_limits, + dispatch: DispatchConfig { + omikron_handler_concurrency: env_usize( + "OMEGA_OMIKRON_HANDLER_CONCURRENCY", + defaults.omikron_handler_concurrency, + )?, + global_handler_concurrency: env_usize( + "OMEGA_GLOBAL_HANDLER_CONCURRENCY", + defaults.global_handler_concurrency, + )?, + }, + }) + } } -fn env_duration(name: &str, fallback: Duration) -> Duration { - env::var(name) - .ok() - .and_then(|value| value.parse::().ok()) - .filter(|value| *value > 0) - .map(Duration::from_secs) - .unwrap_or(fallback) +fn env_usize(name: &str, fallback: usize) -> Result { + match env::var(name) { + Ok(value) => value + .parse::() + .ok() + .filter(|value| *value > 0) + .ok_or_else(|| ConfigError::InvalidValue { + name: name.into(), + value, + }), + Err(env::VarError::NotPresent) => Ok(fallback), + Err(env::VarError::NotUnicode(_)) => Err(ConfigError::InvalidUnicode { name: name.into() }), + } +} + +fn env_duration(name: &str, fallback: Duration) -> Result { + env_usize(name, fallback.as_secs() as usize).map(|value| Duration::from_secs(value as u64)) } diff --git a/src/db/iota_repo.rs b/src/db/iota_repo.rs index 3eedce8..fb50432 100644 --- a/src/db/iota_repo.rs +++ b/src/db/iota_repo.rs @@ -48,20 +48,12 @@ pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) -> } sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)") .bind(id.0) - .bind(public_key.as_bytes()) + .bind(public_key.try_as_bytes()?) .execute(&pool().await?) .await?; Ok(()) } -pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> { - sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?") - .bind(key.as_bytes()) - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} pub async fn delete_iota(id: IotaId) -> Result<()> { sqlx::query("DELETE FROM iotas WHERE id = ?") .bind(id.0) diff --git a/src/db/user_repo.rs b/src/db/user_repo.rs index 79b9034..0d10d4c 100644 --- a/src/db/user_repo.rs +++ b/src/db/user_repo.rs @@ -20,7 +20,7 @@ pub async fn get_register_id() -> Result { .unwrap_or(0); let ts = timestamp as i64; - if ts >= 1 && ts <= MAX_PROTOCOL_ID { + if (1..=MAX_PROTOCOL_ID).contains(&ts) { return Ok(UserId::from(ts)); } @@ -335,15 +335,6 @@ pub async fn change_status(id: UserId, value: String) -> Result<()> { .await } -pub async fn change_presence_preference(id: UserId, value: String) -> Result<()> { - update( - id, - "UPDATE users SET presence_preference = ? WHERE id = ?", - value.into_bytes(), - ) - .await -} - pub async fn change_iota_id(id: UserId, value: Option) -> Result<()> { sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?") .bind(value.map(|id| id.0)) @@ -360,14 +351,6 @@ pub async fn change_token(id: UserId, value: String) -> Result<()> { ) .await } -pub async fn delete_user(id: UserId) -> Result<()> { - sqlx::query("DELETE FROM users WHERE id = ?") - .bind(id.0) - .execute(&pool().await?) - .await?; - Ok(()) -} - /// Delete the central identity while retaining a durable instruction for the /// last hosting Iota. The pending row is intentionally independent of users: /// it must outlive the account row. @@ -422,8 +405,9 @@ pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Re } pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> { + let public_key = public_key.try_as_bytes()?; sqlx::query("UPDATE users SET public_key = ? WHERE id = ?") - .bind(public_key.as_bytes()) + .bind(public_key) .bind(id.0) .execute(&pool().await?) .await?; @@ -446,6 +430,7 @@ pub async fn register_complete_user( if !valid_username(&username) { return Err(OmegaError::Validation("invalid username".into())); } + let public_key_bytes = public_key.try_as_bytes()?; let mut transaction = pool().await?.begin().await?; let lease = sqlx::query( @@ -472,7 +457,7 @@ pub async fn register_complete_user( ) .bind(id.0) .bind(username.as_bytes()) - .bind(public_key.as_bytes()) + .bind(&public_key_bytes) .bind(iota_id.0) .bind(token.as_bytes()) .execute(&mut *transaction) @@ -485,20 +470,20 @@ pub async fn register_complete_user( .bind(id.0) .fetch_optional(&mut *transaction) .await?; - match existing - .map(User::try_from) - .transpose() - .map_err(OmegaError::from)? - { - Some(existing) - if existing.iota_id == Some(iota_id) + let existing_matches = match existing { + Some(existing) => { + let existing = User::try_from(existing).map_err(OmegaError::from)?; + existing.iota_id == Some(iota_id) && existing.username == username - && existing.public_key.as_bytes() == public_key.as_bytes() - && existing.token == token => - { - Ok(()) + && existing.public_key.try_as_bytes()? == public_key_bytes + && existing.token == token } - _ => Err(insert_error.into()), + None => false, + }; + if existing_matches { + Ok(()) + } else { + Err(insert_error.into()) } } }; diff --git a/src/error.rs b/src/error.rs index 9911d25..446afec 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,34 @@ +use std::path::PathBuf; use thiserror::Error; +#[derive(Debug, Error)] +pub enum IdentityError { + #[error("identity storage error at {path}: {source}")] + Storage { + path: PathBuf, + #[source] + source: mtp::files::FileError, + }, + #[error("identity I/O error at {path}: {source}")] + Io { + path: PathBuf, + #[source] + source: std::io::Error, + }, + #[error("public key bundle at {path} does not match the private keyring")] + PublicBundleMismatch { path: PathBuf }, +} + #[derive(Debug, Error)] pub enum OmegaError { #[error("database pool is not initialized")] DatabaseNotInitialized, #[error("database error: {0}")] Database(sqlx::Error), + #[error("cryptographic error: {0}")] + Crypto(#[from] mtp::crypto::CryptoError), + #[error("identity error: {0}")] + Identity(#[from] IdentityError), #[error("invalid input: {0}")] Validation(String), #[error("resource not found")] @@ -45,6 +68,8 @@ impl OmegaError { Self::NotFound => http::StatusCode::NOT_FOUND, Self::DatabaseNotInitialized | Self::Database(_) + | Self::Crypto(_) + | Self::Identity(_) | Self::Transport(_) | Self::NotConnected | Self::NotAuthenticated diff --git a/src/identity.rs b/src/identity.rs new file mode 100644 index 0000000..a7a831d --- /dev/null +++ b/src/identity.rs @@ -0,0 +1,314 @@ +use crate::error::{IdentityError, Result}; +use mtp::crypto::{Keyring, PublicKeyBundle}; +use mtp::files::{ + FileError, load_keyring, load_public_key_bundle, save_keyring, save_public_key_bundle, +}; +use std::{ + fs, + path::{Path, PathBuf}, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; + +pub const KEYRING_PATH: &str = "./omega.mk"; +pub const PUBLIC_KEY_PATH: &str = "./omega.mpkb"; + +pub struct OmegaIdentity { + keyring: Arc, +} + +static PUBLIC_BUNDLE_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); + +impl OmegaIdentity { + pub fn load_or_create(passphrase: &[u8]) -> Result { + Self::load_or_create_at( + Path::new(KEYRING_PATH), + Path::new(PUBLIC_KEY_PATH), + passphrase, + ) + } + + pub(crate) fn load_or_create_at( + keyring_path: impl AsRef, + public_key_path: impl AsRef, + passphrase: &[u8], + ) -> Result { + let keyring_path = keyring_path.as_ref(); + let public_key_path = public_key_path.as_ref(); + let keyring = match load_keyring(keyring_path, passphrase) { + Ok(keyring) => keyring, + Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => { + return Self::create_at(keyring_path, public_key_path, passphrase); + } + Err(error) => { + return Err(IdentityError::Storage { + path: keyring_path.to_path_buf(), + source: error, + } + .into()); + } + }; + + let identity = Self { + keyring: Arc::new(keyring), + }; + match load_public_key_bundle(public_key_path) { + Ok(persisted_bundle) => { + identity.verify_public_bundle(public_key_path, &persisted_bundle)? + } + Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => { + Self::persist_public_bundle(&identity.public_key_bundle(), public_key_path)?; + } + Err(error) => { + return Err(IdentityError::Storage { + path: public_key_path.to_path_buf(), + source: error, + } + .into()); + } + } + Ok(identity) + } + + fn create_at(keyring_path: &Path, public_key_path: &Path, passphrase: &[u8]) -> Result { + let keyring = Keyring::generate(); + save_keyring(&keyring, keyring_path, passphrase).map_err(|error| { + IdentityError::Storage { + path: keyring_path.to_path_buf(), + source: error, + } + })?; + Self::persist_public_bundle(&keyring.public_key_bundle(), public_key_path)?; + Ok(Self { + keyring: Arc::new(keyring), + }) + } + + fn persist_public_bundle(bundle: &PublicKeyBundle, path: &Path) -> Result<()> { + let temporary_path = temporary_path(path)?; + let result = save_public_key_bundle(bundle, &temporary_path).map_err(|source| { + IdentityError::Storage { + path: path.to_path_buf(), + source, + } + }); + if let Err(error) = result { + let _ = fs::remove_file(&temporary_path); + return Err(error.into()); + } + if let Err(source) = fs::File::open(&temporary_path).and_then(|file| file.sync_all()) { + let _ = fs::remove_file(&temporary_path); + return Err(IdentityError::Io { + path: path.to_path_buf(), + source, + } + .into()); + } + if let Err(source) = fs::rename(&temporary_path, path) { + let _ = fs::remove_file(&temporary_path); + return Err(IdentityError::Io { + path: path.to_path_buf(), + source, + } + .into()); + } + Ok(()) + } + + fn verify_public_bundle(&self, path: &Path, persisted_bundle: &PublicKeyBundle) -> Result<()> { + let expected = self.keyring.public_key_bundle().try_as_bytes()?; + let actual = persisted_bundle.try_as_bytes()?; + if expected != actual { + return Err(IdentityError::PublicBundleMismatch { + path: path.to_path_buf(), + } + .into()); + } + Ok(()) + } + + pub fn keyring(&self) -> &Keyring { + &self.keyring + } + + pub fn public_key_bundle(&self) -> PublicKeyBundle { + self.keyring().public_key_bundle() + } + + pub fn clone_keyring(&self) -> Result { + let bytes = self.keyring.try_to_bytes()?; + Ok(Keyring::from_bytes(&bytes)?) + } + + #[cfg(test)] + pub(crate) fn from_keyring(keyring: Keyring) -> Self { + Self { + keyring: Arc::new(keyring), + } + } +} + +fn temporary_path(path: &Path) -> Result { + let parent = path.parent().unwrap_or_else(|| Path::new(".")); + let file_name = path.file_name().ok_or_else(|| IdentityError::Io { + path: path.to_path_buf(), + source: std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "identity path has no file name", + ), + })?; + let mut temporary_name = file_name.to_os_string(); + temporary_name.push(format!( + ".tmp-{}-{}", + std::process::id(), + PUBLIC_BUNDLE_TEMP_COUNTER.fetch_add(1, Ordering::Relaxed) + )); + Ok(parent.join(temporary_name)) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::sync::atomic::{AtomicU64, Ordering}; + + fn test_directory() -> std::path::PathBuf { + static COUNTER: AtomicU64 = AtomicU64::new(0); + let id = COUNTER.fetch_add(1, Ordering::Relaxed); + let path = + std::env::temp_dir().join(format!("omega-identity-test-{}-{id}", std::process::id())); + fs::create_dir_all(&path).expect("create test directory"); + path + } + + #[test] + fn generated_identity_survives_restart_with_separate_file_formats() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let first_bundle = first.public_key_bundle().try_as_bytes().expect("bundle"); + + let keyring_bytes = fs::read(&keyring_path).expect("read keyring"); + let bundle_bytes = fs::read(&public_key_path).expect("read bundle"); + assert_eq!(&keyring_bytes[..4], b"MTMK"); + assert_eq!(&bundle_bytes[..4], b"MPKB"); + + let restarted = + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("reload identity"); + assert_eq!( + restarted + .public_key_bundle() + .try_as_bytes() + .expect("bundle"), + first_bundle + ); + assert_eq!( + load_public_key_bundle(&public_key_path) + .expect("load public bundle") + .try_as_bytes() + .expect("bundle"), + first_bundle + ); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn invalid_existing_keyring_does_not_create_a_new_identity() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + fs::write(&keyring_path, b"not-a-keyring").expect("write invalid keyring"); + + let result = + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"test-passphrase"); + assert!(result.is_err()); + assert!(!public_key_path.exists()); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn missing_public_bundle_is_repaired_without_changing_keyring() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let original_keyring = fs::read(&keyring_path).expect("read keyring"); + fs::remove_file(&public_key_path).expect("remove bundle"); + + let repaired = + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("repair bundle"); + + assert_eq!( + fs::read(&keyring_path).expect("read keyring"), + original_keyring + ); + assert_eq!( + repaired.public_key_bundle().try_as_bytes().expect("bundle"), + load_public_key_bundle(&public_key_path) + .expect("load bundle") + .try_as_bytes() + .expect("bundle") + ); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn mismatched_public_bundle_stops_startup() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let other_keyring = Keyring::generate(); + save_public_key_bundle(&other_keyring.public_key_bundle(), &public_key_path) + .expect("save mismatched bundle"); + + assert!(matches!( + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase), + Err(crate::OmegaError::Identity( + IdentityError::PublicBundleMismatch { .. } + )) + )); + + fs::remove_dir_all(directory).expect("remove test directory"); + } + + #[test] + fn wrong_passphrase_does_not_replace_existing_keyring() { + let directory = test_directory(); + let keyring_path = directory.join("omega.mk"); + let public_key_path = directory.join("omega.mpkb"); + let passphrase = b"test-passphrase"; + + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase) + .expect("create identity"); + let original_keyring = fs::read(&keyring_path).expect("read keyring"); + + assert!( + OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"wrong-passphrase") + .is_err() + ); + assert_eq!( + fs::read(&keyring_path).expect("read keyring"), + original_keyring + ); + + fs::remove_dir_all(directory).expect("remove test directory"); + } +} diff --git a/src/main.rs b/src/main.rs index 16b850e..e04238c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod api; mod config; mod db; pub mod error; +mod identity; mod models; mod server; mod sql; @@ -17,45 +18,18 @@ use crate::transport::omikron_connection; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; +use crate::{config::OmegaConfig, server::middleware}; use dotenv::from_path; -use mtp::crypto::Keyring; -use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; -use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; use std::time::Duration; use tokio::time::interval; - -const KEYRING_PATH: &str = "./omega.mk"; - -static KEYRING: Lazy = Lazy::new(|| { - load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| { - let kr = Keyring::generate(); - if let Err(error) = save_keyring_raw(&kr, KEYRING_PATH) { - eprintln!("Failed to save generated keyring: {error}"); - } - if let Err(error) = save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) { - eprintln!("Failed to save generated public key bundle: {error}"); - } - eprintln!("Generated new keyring at {}", KEYRING_PATH); - kr - }) -}); - -pub fn get_keyring() -> &'static Keyring { - &KEYRING -} -pub fn load_keyring() -> Keyring { - Keyring::from_bytes(&KEYRING.to_bytes()).unwrap_or_else(|error| { - eprintln!("Failed to clone keyring: {error}"); - Keyring::generate() - }) -} +use zeroize::Zeroizing; #[tokio::main] async fn main() { - if let Err(_) = default_provider().install_default() { + if default_provider().install_default().is_err() { println!("Error loading Provider"); return; } @@ -64,6 +38,39 @@ async fn main() { log_in!("Incoming messages"); log_out!("Outgoing messages"); + let identity_secret = match env::var("OMEGA_IDENTITY_SECRET") { + Ok(secret) if !secret.is_empty() => secret, + Ok(_) => { + log!("[FATAL] OMEGA_IDENTITY_SECRET must not be empty"); + return; + } + Err(error) => { + log!("[FATAL] Unable to load OMEGA_IDENTITY_SECRET: {}", error); + return; + } + }; + let identity_secret = Zeroizing::new(identity_secret); + let config = match OmegaConfig::from_env() { + Ok(config) => config, + Err(error) => { + log!("[FATAL] Omega configuration is invalid: {}", error); + return; + } + }; + if middleware::initialize_config(config.rate_limits.clone()).is_err() { + log!("[FATAL] Omega rate-limit configuration was initialized more than once"); + return; + } + let identity = match identity::OmegaIdentity::load_or_create(identity_secret.as_bytes()) { + Ok(identity) => identity, + Err(error) => { + log!("[FATAL] Omega identity initialization failed: {}", error); + return; + } + }; + drop(identity_secret); + let state = OmegaState::new(identity, config); + log!("Started"); log!(" .env"); if let Err(e) = initialize().await { @@ -96,7 +103,7 @@ async fn main() { .unwrap_or(443); tokio::select! { - result = omikron_connection::start(port, OmegaState::new()) => { + result = omikron_connection::start(port, state) => { if let Err(e) = result { log_err!(0, PrintType::General, "Server error: {:?}", e); } diff --git a/src/models/mod.rs b/src/models/mod.rs index 291729d..da21449 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -11,7 +11,8 @@ fn serialize_public_key( where S: serde::Serializer, { - serializer.serialize_str(&key.to_base64()) + let encoded = key.try_to_base64().map_err(serde::ser::Error::custom)?; + serializer.serialize_str(&encoded) } pub use ids::{IotaId, OmikronId, UserId}; diff --git a/src/server/api.rs b/src/server/api.rs index 23debe4..41e5d22 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -8,7 +8,7 @@ use crate::db::{ user_repo::{get_by_user_id, get_by_username}, }; use crate::error::{OmegaError, Result}; -use crate::load_keyring; +use crate::identity::OmegaIdentity; use crate::models::UserId; use crate::server::{ middleware, @@ -24,6 +24,7 @@ use bytes::Bytes; use http::{Method, StatusCode}; use mtp::webserver::{HttpRequest, HttpResponse, RouteParams}; use std::collections::BTreeMap; +use std::sync::Arc; fn error_body(error: &OmegaError) -> String { json(&StatusResponse { @@ -35,11 +36,11 @@ fn error_body(error: &OmegaError) -> String { }) } -fn user_response(user: crate::models::User) -> UserResponse { - UserResponse { +fn user_response(user: crate::models::User) -> Result { + Ok(UserResponse { status: "success", username: user.username, - public_key: user.public_key.to_base64(), + public_key: user.public_key.try_to_base64()?, user_id: user.id.0, iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, @@ -50,10 +51,10 @@ fn user_response(user: crate::models::User) -> UserResponse { avatar: user .avatar .map(|value| base64::engine::general_purpose::STANDARD.encode(value)), - } + }) } -async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { +async fn route(path_parts: &[&str], identity: &OmegaIdentity) -> Result<(StatusCode, String)> { match path_parts { ["api", "get", "omikron"] => { let connection = get_random_omikron() @@ -69,7 +70,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&OmikronResponse { status: "success", id, - public_key: omikron.public_key.to_base64(), + public_key: omikron.public_key.try_to_base64()?, ip_address: omikron.ip_address, port: omikron.port, }), @@ -105,7 +106,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&OmikronResponse { status: "success", id: omikron.id.0, - public_key: omikron.public_key.to_base64(), + public_key: omikron.public_key.try_to_base64()?, ip_address: omikron.ip_address, port: omikron.port, }), @@ -120,12 +121,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { .map(|(omikron_id, iotas)| { let iotas = iotas .into_iter() - .map(|(iota_id, users)| { - ( - iota_id.to_string(), - users.into_iter().map(i64::from).collect(), - ) - }) + .map(|(iota_id, users)| (iota_id.to_string(), users.into_iter().collect())) .collect(); (omikron_id.to_string(), iotas) }) @@ -146,7 +142,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&IotaResponse { status: "success", iota_id: iota.id.0, - public_key: iota.public_key.to_base64(), + public_key: iota.public_key.try_to_base64()?, }), )) } @@ -158,7 +154,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { json(&UsernameResponse { status: "success", username: user.username, - public_key: user.public_key.to_base64(), + public_key: user.public_key.try_to_base64()?, user_id: user.id.0, iota_id: user.iota_id.map(|id| id.0), sub_level: user.sub_level, @@ -168,7 +164,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } ["api", "get", "public_key"] => { let public_key = base64::engine::general_purpose::STANDARD - .encode(load_keyring().public_key_bundle().as_bytes()); + .encode(identity.public_key_bundle().try_as_bytes()?); Ok(( StatusCode::OK, json(&PublicKeyResponse { @@ -180,7 +176,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { ["api", "get", "user", id] => { let id = parse_positive_id(id)?; let user = get_by_user_id(UserId::from(id)).await?; - Ok((StatusCode::OK, json(&user_response(user)))) + Ok((StatusCode::OK, json(&user_response(user)?))) } _ => Ok(( StatusCode::INTERNAL_SERVER_ERROR, @@ -189,13 +185,17 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> { } } -pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpResponse { +pub async fn handle( + request: HttpRequest, + response: HttpResponse, + identity: Arc, +) -> HttpResponse { let method = request.method; let path = request.uri.path().to_string(); if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) { return response .status(StatusCode::TOO_MANY_REQUESTS) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .body(json(&StatusResponse { status: "error_rate_limited", })); @@ -203,7 +203,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons if method == Method::OPTIONS { return response .status(StatusCode::OK) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .header("access-control-allow-methods", "GET, POST, OPTIONS") .header("access-control-allow-headers", "*"); } @@ -213,7 +213,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons return match std::fs::read(file_path) { Ok(bytes) => response .status(StatusCode::OK) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .header("content-type", "application/zip") .header( "content-disposition", @@ -222,7 +222,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons .body(Bytes::from(bytes)), Err(_) => response .status(StatusCode::NOT_FOUND) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .body(json(&StatusResponse { status: "error_not_found", })), @@ -237,12 +237,12 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons .status(StatusCode::TEMPORARY_REDIRECT) .header("location", &location); } - let (status, body) = route(&path_parts) + let (status, body) = route(&path_parts, &identity) .await .unwrap_or_else(|error| (error.status_code(), error_body(&error))); response .status(status) - .header("access-control-allow-origin", &crate::config::cors_origin()) + .header("access-control-allow-origin", crate::config::cors_origin()) .header("access-control-allow-headers", "*") .header("access-control-allow-methods", "GET, POST, OPTIONS") .body(body) @@ -252,6 +252,7 @@ pub async fn handle_pattern( request: HttpRequest, response: HttpResponse, _params: RouteParams, + identity: Arc, ) -> HttpResponse { - handle(request, response).await + handle(request, response, identity).await } diff --git a/src/server/middleware.rs b/src/server/middleware.rs index 5f373ec..b279298 100644 --- a/src/server/middleware.rs +++ b/src/server/middleware.rs @@ -1,14 +1,25 @@ use dashmap::DashMap; use once_cell::sync::Lazy; use std::net::IpAddr; -use std::{collections::VecDeque, time::Instant}; +use std::{collections::VecDeque, sync::OnceLock, time::Instant}; use tokio::time::interval; static REQUESTS: Lazy>> = Lazy::new(DashMap::new); -static CONFIG: Lazy = - Lazy::new(crate::config::RateLimitConfig::from_env); +static CONFIG: OnceLock = OnceLock::new(); const MAX_TRACKED_CLIENT_BUCKETS: usize = 100_000; +pub(crate) fn initialize_config( + config: crate::config::RateLimitConfig, +) -> Result<(), crate::config::RateLimitConfig> { + CONFIG.set(config) +} + +fn config() -> &'static crate::config::RateLimitConfig { + static FALLBACK: Lazy = + Lazy::new(crate::config::RateLimitConfig::from_env_or_default); + CONFIG.get().unwrap_or(&FALLBACK) +} + pub fn allow(remote_addr: IpAddr, path: &str) -> bool { let key = if path.contains("register") { "registration" @@ -23,15 +34,15 @@ pub fn allow(remote_addr: IpAddr, path: &str) -> bool { } } let limit = if key == "registration" { - CONFIG.registration_requests + config().registration_requests } else { - CONFIG.general_requests + config().general_requests }; let now = Instant::now(); let mut entries = REQUESTS.entry(map_key).or_default(); while entries .front() - .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + .is_some_and(|time| now.duration_since(*time) >= config().window) { entries.pop_front(); } @@ -44,7 +55,7 @@ pub fn allow(remote_addr: IpAddr, path: &str) -> bool { pub fn spawn_cleanup_task() -> tokio::task::JoinHandle<()> { tokio::spawn(async { - let mut ticker = interval(CONFIG.window); + let mut ticker = interval(config().window); loop { ticker.tick().await; cleanup_expired(); @@ -57,7 +68,7 @@ fn cleanup_expired() { REQUESTS.retain(|_, entries| { while entries .front() - .is_some_and(|time| now.duration_since(*time) >= CONFIG.window) + .is_some_and(|time| now.duration_since(*time) >= config().window) { entries.pop_front(); } diff --git a/src/server/mod.rs b/src/server/mod.rs index 2d01c5b..64df9e9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,6 +1,6 @@ pub mod api; pub mod index; pub mod middleware; -pub mod server; pub mod short_link; pub mod validation; +pub mod web; diff --git a/src/server/server.rs b/src/server/server.rs deleted file mode 100644 index 9ac4f06..0000000 --- a/src/server/server.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::server::{api, index::index_handler}; -use mtp::webserver::WebServerConfig; - -pub fn build_web_config() -> Result { - WebServerConfig::new() - .route("/api/download/iota_frontend", api::handle)? - .route("/api/get/omikron", api::handle)? - .route("/api/get/connections", api::handle)? - .route("/api/get/public_key", api::handle)? - .route_pattern("/api/get/omikron/{id}", api::handle_pattern)? - .route_pattern("/api/get/iota/{id}", api::handle_pattern)? - .route_pattern("/api/get/id/{username}", api::handle_pattern)? - .route_pattern("/api/get/user/{id}", api::handle_pattern)? - .route_pattern("/direct/{short}", api::handle_pattern)? - .fallback(|_request, response| async move { index_handler(response) }) -} diff --git a/src/server/short_link.rs b/src/server/short_link.rs index dd24ebf..6089957 100644 --- a/src/server/short_link.rs +++ b/src/server/short_link.rs @@ -34,7 +34,7 @@ pub async fn get_short_link(short: &str) -> Result { short }; let frag = short.replace(key, ""); - let normalized = normalize_short(&key); + let normalized = normalize_short(key); let target = short_link_repo::get(&normalized) .await diff --git a/src/server/web.rs b/src/server/web.rs new file mode 100644 index 0000000..5975888 --- /dev/null +++ b/src/server/web.rs @@ -0,0 +1,47 @@ +use crate::identity::OmegaIdentity; +use crate::server::{api, index::index_handler}; +use mtp::webserver::{HttpRequest, HttpResponse, RouteParams, WebServerConfig}; +use std::{future::Future, pin::Pin, sync::Arc}; + +type RouteFuture = Pin + Send>>; + +fn api_handler( + identity: Arc, +) -> impl Fn(HttpRequest, HttpResponse) -> RouteFuture + Send + Sync + 'static { + move |request, response| Box::pin(api::handle(request, response, identity.clone())) +} + +fn api_pattern_handler( + identity: Arc, +) -> impl Fn(HttpRequest, HttpResponse, RouteParams) -> RouteFuture + Send + Sync + 'static { + move |request, response, params| { + Box::pin(api::handle_pattern( + request, + response, + params, + identity.clone(), + )) + } +} + +pub fn build_web_config( + identity: Arc, +) -> Result { + WebServerConfig::new() + .route("/api/download/iota_frontend", api_handler(identity.clone()))? + .route("/api/get/omikron", api_handler(identity.clone()))? + .route("/api/get/connections", api_handler(identity.clone()))? + .route("/api/get/public_key", api_handler(identity.clone()))? + .route_pattern( + "/api/get/omikron/{id}", + api_pattern_handler(identity.clone()), + )? + .route_pattern("/api/get/iota/{id}", api_pattern_handler(identity.clone()))? + .route_pattern( + "/api/get/id/{username}", + api_pattern_handler(identity.clone()), + )? + .route_pattern("/api/get/user/{id}", api_pattern_handler(identity.clone()))? + .route_pattern("/direct/{short}", api_pattern_handler(identity))? + .fallback(|_request, response| async move { index_handler(response) }) +} diff --git a/src/sql/connection_status.rs b/src/sql/connection_status.rs index 104e316..00be3c9 100644 --- a/src/sql/connection_status.rs +++ b/src/sql/connection_status.rs @@ -12,11 +12,7 @@ pub enum UserStatus { iota_online, iota_borked, } -#[allow(unused)] impl UserStatus { - pub fn to_string(&self) -> String { - format!("{:?}", self) - } pub fn from_client_preference(s: &str) -> Option { match s { "user_online" => Some(Self::user_online), @@ -38,11 +34,18 @@ impl UserStatus { /// Parse a value received from a client or persisted as an account /// preference. Derived connectivity and diagnostic states are never valid /// preferences. + #[allow(dead_code)] pub fn from_str(s: &str) -> Option { Self::from_client_preference(s) } } +impl std::fmt::Display for UserStatus { + fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(formatter, "{self:?}") + } +} + #[cfg(test)] mod tests { use super::UserStatus; diff --git a/src/sql/user_online_tracker.rs b/src/sql/user_online_tracker.rs index be3e87c..cac8ab6 100644 --- a/src/sql/user_online_tracker.rs +++ b/src/sql/user_online_tracker.rs @@ -275,10 +275,6 @@ impl PresenceTracker { Ok(()) } - pub fn check_index_consistency(&self) -> Result<(), String> { - Self::check_indices(&self.routes.read().unwrap()) - } - pub fn set_preference(&self, user_id: i64, status: UserStatus) { self.preferences.write().unwrap().insert(user_id, status); } @@ -335,14 +331,6 @@ impl PresenceTracker { }) } - pub fn track_iota(&self, iota_id: i64, omikron_id: i64) { - self.connect_iota(iota_id, omikron_id); - } - - pub fn untrack_iota(&self, iota_id: i64, omikron_id: i64) -> bool { - self.disconnect_iota(iota_id, omikron_id) - } - pub fn track_session(&self, user_id: i64, session_id: i64, omikron_id: i64, iota_id: i64) { let mut routes = self.routes.write().unwrap(); Self::insert_session_locked( @@ -373,15 +361,6 @@ impl PresenceTracker { .is_some_and(|route| route.omikron_id == omikron_id) } - pub fn session_route(&self, user_id: i64, session_id: i64) -> Option { - self.routes - .read() - .unwrap() - .sessions - .get(&(user_id, session_id)) - .map(|route| route.clone()) - } - pub fn resolve_public_state(&self, user_id: i64, iota_id: i64) -> UserStatus { if !self.has_iota_route(iota_id) { return UserStatus::iota_offline; @@ -496,12 +475,6 @@ impl PresenceTracker { Self::debug_assert_indices(&routes); } - pub fn remove_subscription(&self, user_id: i64, session_id: i64) { - let mut routes = self.routes.write().unwrap(); - Self::remove_subscription_locked(&mut routes, (user_id, session_id)); - Self::debug_assert_indices(&routes); - } - pub fn subscribers(&self, target_user_id: i64) -> Vec { let mut subscribers = self .routes @@ -542,15 +515,6 @@ impl PresenceTracker { sessions } - pub fn replace_routes_owned_by( - &self, - omikron_id: i64, - iota_ids: &[i64], - sessions: &[(i64, i64, i64)], - ) { - self.replace_omikron_snapshot(omikron_id, iota_ids, sessions); - } - pub fn replace_omikron_snapshot( &self, omikron_id: i64, @@ -621,10 +585,6 @@ impl PresenceTracker { ) } - pub fn track_iota_connection(&self, iota_id: i64, omikron_id: i64, primary: bool) { - self.connect_iota_with_priority(iota_id, omikron_id, primary); - } - pub fn untrack_iota_connection(&self, iota_id: i64, omikron_id: i64) -> bool { let connections_empty = self.disconnect_iota(iota_id, omikron_id); self.remove_sessions_for_iota(iota_id, omikron_id); @@ -671,29 +631,6 @@ impl PresenceTracker { user_routes.into_iter().map(|(_, route)| route).collect() } - pub fn sessions_for_user(&self, user_id: i64) -> Vec<(i64, ClientSessionRoute)> { - let routes = self.routes.read().unwrap(); - let mut sessions = routes - .indices - .sessions_by_user - .get(&user_id) - .into_iter() - .flat_map(|session_ids| session_ids.iter()) - .filter_map(|session_id| { - routes - .sessions - .get(&(user_id, *session_id)) - .map(|route| (*session_id, route.clone())) - }) - .collect::>(); - sessions.sort_by_key(|(session_id, route)| (*session_id, route.omikron_id)); - sessions - } - - pub fn route_for_session(&self, user_id: i64, session_id: i64) -> Option { - self.session_route(user_id, session_id) - } - pub fn user_route(&self, user_id: i64) -> Option { self.routes_for_user(user_id).into_iter().next() } @@ -745,10 +682,6 @@ impl PresenceTracker { } result } - - pub fn remove_omikron_and_offline_users(&self, omikron_id: i64) -> RemovedOmikronState { - self.remove_omikron(omikron_id) - } } #[cfg(test)] @@ -756,6 +689,10 @@ mod tests { use super::{ClientSessionRoute, PresenceTracker}; use crate::sql::connection_status::UserStatus; + fn check_index_consistency(tracker: &PresenceTracker) -> Result<(), String> { + PresenceTracker::check_indices(&tracker.routes.read().unwrap()) + } + #[test] fn effective_state_is_derived_from_routes_and_preference() { let tracker = PresenceTracker::default(); @@ -765,7 +702,7 @@ mod tests { UserStatus::iota_offline ); - tracker.track_iota(11, 42); + tracker.connect_iota(11, 42); assert_eq!( tracker.resolve_public_state(7, 11), UserStatus::user_offline @@ -783,7 +720,7 @@ mod tests { #[test] fn session_must_match_assigned_iota_and_live_route() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); + tracker.connect_iota(11, 42); tracker.track_session(7, 3, 42, 12); assert!(!tracker.has_active_session_for_iota(7, 11)); assert_eq!( @@ -807,7 +744,7 @@ mod tests { #[test] fn resolver_covers_all_public_preference_states() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); + tracker.connect_iota(11, 42); tracker.track_session(7, 3, 42, 11); for (preference, expected) in [ @@ -826,14 +763,20 @@ mod tests { fn session_routes_replace_by_session_without_replacing_preferences() { let tracker = PresenceTracker::default(); tracker.set_preference(7, UserStatus::user_dnd); - tracker.track_iota(11, 42); - tracker.track_iota(12, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(12, 43); tracker.track_session(7, 3, 42, 11); tracker.track_session(7, 4, 43, 12); assert_eq!(tracker.preference(7), UserStatus::user_dnd); assert_eq!( - tracker.session_route(7, 3), + tracker + .routes + .read() + .unwrap() + .sessions + .get(&(7, 3)) + .cloned(), Some(ClientSessionRoute { omikron_id: 42, iota_id: 11, @@ -865,21 +808,21 @@ mod tests { #[test] fn losing_one_iota_route_keeps_iota_online() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); - tracker.track_iota(11, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); assert!(tracker.has_iota_route(11)); - assert!(!tracker.untrack_iota(11, 42)); + assert!(!tracker.disconnect_iota(11, 42)); assert!(tracker.has_iota_route(11)); - assert!(tracker.untrack_iota(11, 43)); + assert!(tracker.disconnect_iota(11, 43)); assert!(!tracker.has_iota_route(11)); } #[test] fn iota_connection_routes_track_primary_and_replacement() { let tracker = PresenceTracker::default(); - tracker.track_iota_connection(11, 42, true); - tracker.track_iota_connection(11, 43, false); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); assert_eq!(tracker.iota_connections(11), Some(vec![42, 43])); assert_eq!(tracker.primary_iota_route(11), Some(42)); @@ -961,8 +904,8 @@ mod tests { #[test] fn active_session_can_use_one_of_several_live_iota_routes() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); - tracker.track_iota(11, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); tracker.track_session(7, 3, 43, 11); assert!(tracker.has_active_session_for_iota(7, 11)); @@ -973,13 +916,13 @@ mod tests { #[test] fn route_snapshot_replaces_only_one_omikron() { let tracker = PresenceTracker::default(); - tracker.track_iota(11, 42); - tracker.track_iota(12, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(12, 43); tracker.track_session(7, 3, 42, 11); tracker.track_session(8, 4, 43, 12); tracker.replace_subscription(7, 3, 42, vec![20]); - tracker.replace_routes_owned_by(42, &[12], &[(9, 5, 12)]); + tracker.replace_omikron_snapshot(42, &[12], &[(9, 5, 12)]); assert!(!tracker.owns_session(7, 3, 42)); assert!(tracker.owns_session(8, 4, 43)); @@ -992,8 +935,8 @@ mod tests { fn omikron_cleanup_preserves_other_routes_and_preferences() { let tracker = PresenceTracker::default(); tracker.set_preference(7, UserStatus::user_dnd); - tracker.track_iota(11, 42); - tracker.track_iota(11, 43); + tracker.connect_iota(11, 42); + tracker.connect_iota(11, 43); tracker.track_session(7, 3, 42, 11); tracker.track_session(7, 4, 43, 11); tracker.replace_subscription(7, 3, 42, vec![20]); @@ -1036,14 +979,14 @@ mod tests { .cloned(), Some([20, 21].into_iter().collect()) ); - assert!(tracker.check_index_consistency().is_ok()); + assert!(check_index_consistency(&tracker).is_ok()); tracker.replace_subscription(7, 3, 42, vec![22]); assert!(tracker.subscribers(20).is_empty()); assert!(tracker.subscribers(21).is_empty()); assert_eq!(tracker.subscribers(22).len(), 1); tracker.remove_session(7, 3, 42); - assert!(tracker.check_index_consistency().is_ok()); + assert!(check_index_consistency(&tracker).is_ok()); assert!( tracker .routes @@ -1076,7 +1019,7 @@ mod tests { assert_eq!(removed.iota_ids, vec![11]); assert_eq!(removed.session_user_ids, vec![7]); assert_eq!(removed.subscriber_sessions, vec![(7, 3)]); - assert!(tracker.check_index_consistency().is_ok()); + assert!(check_index_consistency(&tracker).is_ok()); } #[test] @@ -1093,6 +1036,6 @@ mod tests { .unwrap() .clear(); - assert!(tracker.check_index_consistency().is_err()); + assert!(check_index_consistency(&tracker).is_err()); } } diff --git a/src/state.rs b/src/state.rs index 02c899b..215f1b3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,7 +1,10 @@ -use crate::sql::user_online_tracker::PresenceTracker; +use crate::{ + config::OmegaConfig, identity::OmegaIdentity, sql::user_online_tracker::PresenceTracker, +}; use dashmap::DashMap; use std::sync::Arc; use std::time::{Duration, Instant}; +use tokio::sync::Semaphore; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum AccountChallengeOperation { @@ -11,28 +14,50 @@ pub enum AccountChallengeOperation { #[derive(Clone, Debug)] pub struct AccountChallenge { - pub operation: AccountChallengeOperation, - pub user_id: i64, - pub requester_iota_id: i64, pub nonce: u64, pub created_at: Instant, } pub struct OmegaState { pub presence: Arc, + pub identity: Arc, + pub(crate) config: Arc, + pub(crate) global_handler_limit: Arc, + pub(crate) omikron_handler_concurrency: usize, challenges: DashMap<(AccountChallengeOperation, i64, i64), AccountChallenge>, } -impl Default for OmegaState { - fn default() -> Self { +impl OmegaState { + fn with_handler_limits(identity: OmegaIdentity, config: OmegaConfig) -> Self { + let omikron_handler_concurrency = config.dispatch.omikron_handler_concurrency; + let global_handler_concurrency = config.dispatch.global_handler_concurrency; Self { presence: Arc::new(PresenceTracker::default()), + identity: Arc::new(identity), + config: Arc::new(config), + global_handler_limit: Arc::new(Semaphore::new(global_handler_concurrency)), + omikron_handler_concurrency, challenges: DashMap::new(), } } -} -impl OmegaState { + pub fn new(identity: OmegaIdentity, config: OmegaConfig) -> Arc { + Arc::new(Self::with_handler_limits(identity, config)) + } + + #[cfg(test)] + fn test_state() -> Arc { + use mtp::crypto::Keyring; + + let mut config = OmegaConfig::default(); + config.dispatch.omikron_handler_concurrency = 4; + config.dispatch.global_handler_concurrency = 8; + Arc::new(Self::with_handler_limits( + OmegaIdentity::from_keyring(Keyring::generate()), + config, + )) + } + pub fn issue_challenge( &self, operation: AccountChallengeOperation, @@ -43,9 +68,6 @@ impl OmegaState { self.challenges.insert( (operation, user_id, requester_iota_id), AccountChallenge { - operation, - user_id, - requester_iota_id, nonce, created_at: Instant::now(), }, @@ -68,12 +90,6 @@ impl OmegaState { } } -impl OmegaState { - pub fn new() -> Arc { - Arc::new(Self::default()) - } -} - #[cfg(test)] mod tests { use super::OmegaState; @@ -81,10 +97,10 @@ mod tests { #[test] fn state_instances_have_independent_presence_trackers() { - let first = OmegaState::new(); - let second = OmegaState::new(); + let first = OmegaState::test_state(); + let second = OmegaState::test_state(); - first.presence.track_iota_connection(11, 42, true); + first.presence.connect_iota(11, 42); assert!(first.presence.has_iota_route(11)); assert!(!second.presence.has_iota_route(11)); @@ -94,10 +110,10 @@ mod tests { #[test] fn two_session_private_and_public_presence_flow_is_authoritative() { - let state = OmegaState::new(); + let state = OmegaState::test_state(); state.presence.set_preference(7, UserStatus::user_online); state.presence.set_preference(8, UserStatus::user_online); - state.presence.track_iota_connection(11, 42, true); + state.presence.connect_iota(11, 42); state.presence.track_session(7, 100, 42, 11); state.presence.track_session(7, 101, 42, 11); state.presence.replace_subscription(7, 100, 42, vec![8]); diff --git a/src/transport/connection.rs b/src/transport/connection.rs index 6cada3e..66d9eb3 100644 --- a/src/transport/connection.rs +++ b/src/transport/connection.rs @@ -2,24 +2,94 @@ pub(crate) use super::omikron_connection::{OmikronConnection, OmikronResult}; use mtp::codec::{CommunicationValue, DataValue}; -pub(crate) trait MtpValueCompat { - fn get_id(&self) -> u32; - fn get_sender(&self) -> u64; - fn get_receiver(&self) -> u64; +pub(crate) trait RequiredMtpFields { + /// Require a nonzero request correlation ID. `None` and `Some(0)` are + /// distinct MTP wire states, but both are invalid for Omega requests. + fn require_id(&self) -> OmikronResult; + /// Require a nonzero authenticated peer identity. + fn require_sender(&self) -> OmikronResult; + /// Require a nonzero application routing target. + fn require_receiver(&self) -> OmikronResult; + fn require_sender_i64(&self) -> OmikronResult; + fn require_receiver_i64(&self) -> OmikronResult; } -impl MtpValueCompat for CommunicationValue { - fn get_id(&self) -> u32 { - self.id().unwrap_or_default() +impl RequiredMtpFields for CommunicationValue { + fn require_id(&self) -> OmikronResult { + self.id().filter(|id| *id != 0).ok_or_else(|| { + crate::OmegaError::Validation("MTP message is missing request id".into()) + }) } - fn get_sender(&self) -> u64 { - self.sender().unwrap_or_default() + fn require_sender(&self) -> OmikronResult { + self.sender() + .filter(|sender| *sender != 0) + .ok_or_else(|| crate::OmegaError::Validation("MTP message is missing sender".into())) } - fn get_receiver(&self) -> u64 { - self.receiver().unwrap_or_default() + fn require_receiver(&self) -> OmikronResult { + self.receiver() + .filter(|receiver| *receiver != 0) + .ok_or_else(|| crate::OmegaError::Validation("MTP message is missing receiver".into())) } + + fn require_sender_i64(&self) -> OmikronResult { + positive_i64(self.require_sender()?, "sender") + } + + fn require_receiver_i64(&self) -> OmikronResult { + positive_i64(self.require_receiver()?, "receiver") + } +} + +pub(crate) fn positive_i64(value: impl TryInto, field: &str) -> OmikronResult { + let value = value + .try_into() + .map_err(|_| crate::OmegaError::Validation(format!("invalid {field}")))?; + let value = i64::try_from(value) + .map_err(|_| crate::OmegaError::Validation(format!("invalid {field}")))?; + if value <= 0 { + return Err(crate::OmegaError::Validation(format!("invalid {field}"))); + } + Ok(value) +} + +pub(crate) fn validate_dispatch_fields(value: &CommunicationValue) -> OmikronResult<()> { + let Some(message_type) = value.get_comm_type_enum() else { + return Err(crate::OmegaError::Validation( + "MTP message has an unknown communication type".into(), + )); + }; + + if !matches!( + message_type, + mtp::codec::CommunicationType::ClientChanged + | mtp::codec::CommunicationType::PushNotification + ) { + value.require_id()?; + } + + if matches!( + message_type, + mtp::codec::CommunicationType::GetUserData + | mtp::codec::CommunicationType::ChangeUserData + | mtp::codec::CommunicationType::ChangeIotaData + | mtp::codec::CommunicationType::DeleteUser + | mtp::codec::CommunicationType::AttachUserBegin + | mtp::codec::CommunicationType::AttachUserComplete + | mtp::codec::CommunicationType::DeleteUserCredentialBegin + | mtp::codec::CommunicationType::DeleteUserCredentialComplete + | mtp::codec::CommunicationType::EraseHostedUserDataAck + | mtp::codec::CommunicationType::ReleaseUserFromIota + | mtp::codec::CommunicationType::DeleteIota + | mtp::codec::CommunicationType::GetNotifications + | mtp::codec::CommunicationType::ReadNotification + | mtp::codec::CommunicationType::StateSubscribe + ) { + value.require_sender()?; + } + + Ok(()) } pub(crate) trait OptionalDataValueCompat { @@ -46,3 +116,65 @@ impl OptionalDataValueCompat for Option<&DataValue> { self.and_then(|value| value.as_bytes()) } } + +#[cfg(test)] +mod tests { + use super::{RequiredMtpFields, validate_dispatch_fields}; + use mtp::codec::{CommunicationType, CommunicationValue}; + + #[test] + fn missing_request_id_is_rejected_without_a_zero_fallback() { + let value = CommunicationValue::new(CommunicationType::GetUserData).without_id(); + assert!(value.require_id().is_err()); + assert!(validate_dispatch_fields(&value).is_err()); + } + + #[test] + fn zero_routing_values_are_rejected_by_omega_contract() { + let value = CommunicationValue::new(CommunicationType::GetUserData) + .with_id(0) + .with_sender(0) + .with_receiver(0); + + assert!(value.require_id().is_err()); + assert!(value.require_sender().is_err()); + assert!(value.require_receiver().is_err()); + assert!(validate_dispatch_fields(&value).is_err()); + } + + #[test] + fn security_sensitive_messages_require_sender() { + for message_type in [ + CommunicationType::GetUserData, + CommunicationType::ChangeUserData, + CommunicationType::ChangeIotaData, + CommunicationType::DeleteUser, + CommunicationType::AttachUserBegin, + CommunicationType::AttachUserComplete, + CommunicationType::DeleteUserCredentialBegin, + CommunicationType::DeleteUserCredentialComplete, + CommunicationType::EraseHostedUserDataAck, + CommunicationType::ReleaseUserFromIota, + CommunicationType::DeleteIota, + CommunicationType::GetNotifications, + CommunicationType::ReadNotification, + CommunicationType::StateSubscribe, + ] { + let value = CommunicationValue::new(message_type).with_id(1); + assert!(value.require_sender().is_err()); + assert!(validate_dispatch_fields(&value).is_err()); + } + } + + #[test] + fn optional_client_changed_id_remains_optional() { + let value = CommunicationValue::new(CommunicationType::ClientChanged).without_id(); + assert!(validate_dispatch_fields(&value).is_ok()); + } + + #[test] + fn push_notification_uses_its_logical_sender_field() { + let value = CommunicationValue::new(CommunicationType::PushNotification); + assert!(validate_dispatch_fields(&value).is_ok()); + } +} diff --git a/src/transport/handlers/account.rs b/src/transport/handlers/account.rs index 0374f78..ae7215f 100644 --- a/src/transport/handlers/account.rs +++ b/src/transport/handlers/account.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::{iota_repo, user_repo}, @@ -22,24 +22,30 @@ async fn delete( Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), }; - connection.send(&response.with_id(value.get_id())).await + connection + .send(&response.with_id(value.require_id()?)) + .await } pub async fn user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let user_id = UserId::from(value.get_sender() as i64); + value.require_id()?; + value.require_sender_i64()?; + let user_id = UserId::from(value.require_sender_i64()?); complete_delete(connection, value, user_id).await } pub async fn iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; delete( connection, value.clone(), - iota_repo::delete_iota(IotaId::from(value.get_sender() as i64)), + iota_repo::delete_iota(IotaId::from(value.require_sender_i64()?)), ) .await } @@ -48,6 +54,8 @@ pub async fn release_from_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -55,18 +63,21 @@ pub async fn release_from_iota( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let requester = IotaId::from(value.get_sender() as i64); + let requester = IotaId::from(value.require_sender_i64()?); let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; }; if user.iota_id != Some(requester) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await; } let previous_iota = user.iota_id; @@ -76,14 +87,17 @@ pub async fn release_from_iota( crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; } connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Err(error) => { connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await @@ -104,6 +118,8 @@ pub async fn attach_begin( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -111,7 +127,7 @@ pub async fn attach_begin( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; if user_repo::get_by_user_id(UserId::from(user_id)) @@ -119,10 +135,10 @@ pub async fn attach_begin( .is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; } - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let nonce = connection .state() @@ -130,7 +146,7 @@ pub async fn attach_begin( connection .send( &CommunicationValue::new(CommunicationType::AttachUserChallenge) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), ) @@ -141,6 +157,8 @@ pub async fn attach_complete( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -148,24 +166,30 @@ pub async fn attach_complete( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let Some(nonce) = value .get_data(DataType::ServerNonce) .as_signed_number() .and_then(|v| u64::try_from(v).ok()) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; let signature = value.get_data(DataType::Signature).as_bytes(); let pq_signature = value.get_data(DataType::PqSignature).as_bytes(); let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; if !connection.state().consume_challenge( @@ -175,12 +199,15 @@ pub async fn attach_complete( nonce, ) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; } let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; }; let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce); @@ -188,7 +215,10 @@ pub async fn attach_complete( || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await; } let previous_iota = user.iota_id; @@ -199,12 +229,15 @@ pub async fn attach_complete( } crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Err(_) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .send_error_response(value.require_id()?, CommunicationType::ErrorInternal) .await } } @@ -225,7 +258,7 @@ async fn complete_delete( connection .send( &CommunicationValue::new(CommunicationType::Success) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default( DataType::CleanupPending, DataValue::Bool(cleanup_pending), @@ -235,14 +268,14 @@ async fn complete_delete( } Err(crate::error::OmegaError::NotFound) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await } Err(error) => { connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await @@ -254,6 +287,8 @@ pub async fn delete_credential_begin( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -261,7 +296,7 @@ pub async fn delete_credential_begin( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; if user_repo::get_by_user_id(UserId::from(user_id)) @@ -269,10 +304,10 @@ pub async fn delete_credential_begin( .is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; } - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let nonce = connection .state() @@ -280,7 +315,7 @@ pub async fn delete_credential_begin( connection .send( &CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) .add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())), ) @@ -291,6 +326,8 @@ pub async fn delete_credential_complete( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -298,17 +335,20 @@ pub async fn delete_credential_complete( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let requester = value.get_sender() as i64; + let requester = value.require_sender_i64()?; let Some(nonce) = value .get_data(DataType::ServerNonce) .as_signed_number() .and_then(|v| u64::try_from(v).ok()) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; let (Some(signature), Some(pq_signature)) = ( @@ -316,7 +356,10 @@ pub async fn delete_credential_complete( value.get_data(DataType::PqSignature).as_bytes(), ) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; }; if !connection.state().consume_challenge( @@ -326,12 +369,15 @@ pub async fn delete_credential_complete( nonce, ) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorInvalidChallenge, + ) .await; } let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; }; let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce); @@ -339,7 +385,10 @@ pub async fn delete_credential_complete( || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await; } complete_delete(connection, value, user.id).await @@ -349,6 +398,8 @@ pub async fn erase_hosted_user_data_ack( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + value.require_id()?; + value.require_sender_i64()?; let Some(user_id) = value .get_data(DataType::UserId) .as_signed_number() @@ -356,45 +407,31 @@ pub async fn erase_hosted_user_data_ack( .filter(|v| *v > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId) .await; }; - let iota_id = IotaId::from(value.get_sender() as i64); + let iota_id = IotaId::from(value.require_sender_i64()?); match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await { Ok(true) => { connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Ok(false) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated) + .send_error_response( + value.require_id()?, + CommunicationType::ErrorNotAuthenticated, + ) .await } Err(_) => { connection - .send_error_response(value.get_id(), CommunicationType::ErrorInternal) + .send_error_response(value.require_id()?, CommunicationType::ErrorInternal) .await } } } - -/// New lifecycle operation names are intentionally fail-closed until their -/// proof and durable-erasure handlers are enabled. This explicit dispatch -/// prevents either a bare Iota request or the legacy DeleteUser path from -/// acquiring account-deletion authority during a staged rollout. -pub async fn lifecycle_unavailable( - connection: Arc, - value: CommunicationValue, -) -> OmikronResult<()> { - connection - .send( - &CommunicationValue::new(CommunicationType::ErrorNotAuthenticated) - .with_id(value.get_id()) - .add_typed_default( - DataType::ErrorType, - DataValue::Str("user lifecycle proof handler is not enabled".into()), - ), - ) - .await -} diff --git a/src/transport/handlers/links.rs b/src/transport/handlers/links.rs index ad45c05..10df9e4 100644 --- a/src/transport/handlers/links.rs +++ b/src/transport/handlers/links.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::server::short_link::add_short_link; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; @@ -17,7 +17,7 @@ pub async fn shorten( .await .map_err(|_| crate::error::OmegaError::Transport("short link error".to_string()))?; let response = CommunicationValue::new(CommunicationType::ShortenLink) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::Link, DataValue::Str(short)); connection.send(&response).await } diff --git a/src/transport/handlers/notifications.rs b/src/transport/handlers/notifications.rs index 9405bf0..59883c5 100644 --- a/src/transport/handlers/notifications.rs +++ b/src/transport/handlers/notifications.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{db::notification_repo, log, models::UserId}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; @@ -10,35 +10,36 @@ pub async fn get( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let notifications = - match notification_repo::get_notifications(UserId::from(value.get_sender() as i64)).await { - Ok(items) => items - .into_iter() - .map(|item| { - let tm = TypeMap::latest(); - let Some(sender) = DataType::SenderId.try_to_id(&tm) else { - return DataValue::Container(Vec::new()); - }; - let Some(amount) = DataType::Amount.try_to_id(&tm) else { - return DataValue::Container(Vec::new()); - }; - DataValue::Container(vec![ - (sender, DataValue::SignedNumber(item.sender_id.0.into())), - (amount, DataValue::SignedNumber(item.amount.into())), - ]) - }) - .collect(), - Err(error) => { - log!( - crate::util::logger::PrintType::General, - "SQL get_notifications error: {}", - error - ); - Vec::new() - } - }; + let request_id = value.require_id()?; + let sender = value.require_sender_i64()?; + let notifications = match notification_repo::get_notifications(UserId::from(sender)).await { + Ok(items) => items + .into_iter() + .map(|item| { + let tm = TypeMap::latest(); + let Some(sender) = DataType::SenderId.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + let Some(amount) = DataType::Amount.try_to_id(&tm) else { + return DataValue::Container(Vec::new()); + }; + DataValue::Container(vec![ + (sender, DataValue::SignedNumber(item.sender_id.0.into())), + (amount, DataValue::SignedNumber(item.amount.into())), + ]) + }) + .collect(), + Err(error) => { + log!( + crate::util::logger::PrintType::General, + "SQL get_notifications error: {}", + error + ); + Vec::new() + } + }; let response = CommunicationValue::new(CommunicationType::GetNotifications) - .with_id(value.get_id()) + .with_id(request_id) .add_typed_default(DataType::Notifications, DataValue::Array(notifications)); connection.send(&response).await } @@ -47,19 +48,17 @@ pub async fn read( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let receiver = match value.get_sender() { - sender if sender > 0 => sender as i64, - _ => match value.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, - }; + let request_id = value.require_id()?; + let receiver = value.require_sender_i64()?; let Some(other) = value .get_data(DataType::SenderId) .as_number() - .map(|id| id as i64) + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { - return Ok(()); + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; }; if let Err(error) = notification_repo::read_notification(UserId::from(receiver), UserId::from(other)).await @@ -71,7 +70,7 @@ pub async fn read( ); } else { let response = - CommunicationValue::new(CommunicationType::ReadNotification).with_id(value.get_id()); + CommunicationValue::new(CommunicationType::ReadNotification).with_id(request_id); let _ = connection.send(&response).await; let sync = CommunicationValue::new(CommunicationType::ReadNotification) .with_receiver(receiver as u64) @@ -85,18 +84,54 @@ pub async fn push( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let receiver = match value.get_receiver() { - receiver if receiver > 0 => receiver as i64, - _ => match value.get_data(DataType::ReceiverId).as_number() { - Some(id) => id as i64, - None => return Ok(()), - }, + let request_id = value.id().filter(|id| *id != 0); + let receiver = value.require_receiver_i64().ok().or_else(|| { + value + .get_data(DataType::ReceiverId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + }); + let Some(receiver) = receiver else { + if let Some(request_id) = request_id { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + return Ok(()); }; - let sender = value + let Some(sender) = value .get_data(DataType::SenderId) .as_number() - .map(|id| id as i64) - .unwrap_or(value.get_sender() as i64); + .and_then(|sender| i64::try_from(sender).ok()) + .filter(|sender| *sender > 0) + else { + if let Some(request_id) = request_id { + return connection + .send_error_response(request_id, CommunicationType::ErrorInvalidData) + .await; + } + return Ok(()); + }; + let source_omikron = connection + .clone() + .get_omikron_id() + .await + .ok_or(crate::OmegaError::NotConnected)?; + if !connection + .state() + .presence + .routes_for_user(sender) + .iter() + .any(|route| route.omikron_id == source_omikron) + { + if let Some(request_id) = request_id { + return connection + .send_error_response(request_id, CommunicationType::ErrorNotAuthenticated) + .await; + } + return Ok(()); + } if let Err(error) = notification_repo::add_notification(UserId::from(receiver), UserId::from(sender)).await { @@ -106,9 +141,11 @@ pub async fn push( error ); } else { - let response = - CommunicationValue::new(CommunicationType::PushNotification).with_id(value.get_id()); - let _ = connection.send(&response).await; + if let Some(request_id) = request_id { + let response = + CommunicationValue::new(CommunicationType::PushNotification).with_id(request_id); + let _ = connection.send(&response).await; + } let push = CommunicationValue::new(CommunicationType::PushNotification) .with_receiver(receiver as u64) .add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender.into())); diff --git a/src/transport/handlers/presence.rs b/src/transport/handlers/presence.rs index 5c631bf..a958a92 100644 --- a/src/transport/handlers/presence.rs +++ b/src/transport/handlers/presence.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::user_repo, log_in, models::IotaId, sql::connection_status::UserStatus, state::OmegaState, @@ -11,7 +11,10 @@ use std::{ }; fn parse_subscription(value: &CommunicationValue) -> Result<(i64, i64, Vec), &'static str> { - let user_id = i64::try_from(value.get_sender()) + let Some(sender) = value.sender() else { + return Err("user_id"); + }; + let user_id = i64::try_from(sender) .ok() .filter(|id| *id > 0) .ok_or("user_id")?; @@ -51,13 +54,11 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa users .iter() .map(|user| { - ( - user.id.0, - state.presence.resolve_public_state( - user.id.0, - user.iota_id.map(|id| id.0).unwrap_or_default(), - ), - ) + let status = user + .iota_id + .map(|iota_id| state.presence.resolve_public_state(user.id.0, iota_id.0)) + .unwrap_or(UserStatus::user_offline); + (user.id.0, status) }) .collect() } @@ -70,9 +71,10 @@ fn changed_states( let mut changes = users .iter() .filter_map(|user| { - let after = state - .presence - .resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()); + let after = user + .iota_id + .map(|iota_id| state.presence.resolve_public_state(user.id.0, iota_id.0)) + .unwrap_or(UserStatus::user_offline); (before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after)) }) .collect::>(); @@ -96,21 +98,6 @@ fn state_notification( .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) } -fn private_state_notification( - user_id: i64, - session_id: i64, - user_state: &UserStatus, -) -> CommunicationValue { - CommunicationValue::new(CommunicationType::ClientChanged) - .with_receiver(user_id as u64) - .add_typed_default( - DataType::SessionId, - DataValue::SignedNumber(session_id.into()), - ) - .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) - .add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string())) -} - async fn publish_state_changes(state: &OmegaState, changes: &[(i64, UserStatus)]) { let mut grouped = BTreeMap::>::new(); for (user_id, user_state) in changes { @@ -143,28 +130,6 @@ async fn publish_changed_states( publish_state_changes(state, &changed_states(state, before, users)).await; } -async fn publish_private_state(state: &OmegaState, user_id: i64, user_state: &UserStatus) { - let mut grouped = BTreeMap::>::new(); - for (session_id, route) in state.presence.sessions_for_user(user_id) { - grouped - .entry(route.omikron_id) - .or_default() - .push(private_state_notification(user_id, session_id, user_state)); - } - for (omikron_id, notifications) in grouped { - if let Err(error) = - crate::transport::omikron_manager::send_state_batch(omikron_id, notifications).await - { - log_in!( - crate::util::logger::PrintType::General, - "Failed to deliver private presence state batch to Omikron {}: {}", - omikron_id, - error - ); - } - } -} - pub async fn state_subscribe( state: Arc, connection: Arc, @@ -175,13 +140,13 @@ pub async fn state_subscribe( Ok(subscription) => subscription, Err("user_id") => { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) + .send_error_response(value.require_id()?, CommunicationType::ErrorNoUserId) .await; } Err(detail) => { return connection .send_error_response_with_detail( - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, detail, ) @@ -190,14 +155,14 @@ pub async fn state_subscribe( }; if !state.presence.owns_session(user_id, session_id, omikron_id) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .send_error_response(value.require_id()?, CommunicationType::ErrorNoIota) .await; } state .presence .replace_subscription(user_id, session_id, omikron_id, user_ids); connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -242,7 +207,7 @@ pub async fn user_connected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let Some(session_id) = value @@ -252,7 +217,7 @@ pub async fn user_connected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let Some(iota_id) = value @@ -262,25 +227,25 @@ pub async fn user_connected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let user = match user_repo::get_by_user_id(user_id.into()).await { Ok(user) => user, Err(crate::error::OmegaError::NotFound) => { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(value.require_id()?, CommunicationType::ErrorNotFound) .await; } - Err(error) => return Err(error.into()), + Err(error) => return Err(error), }; let preferences = match user_repo::get_presence_preferences(&[user_id]).await { Ok(preferences) => preferences, - Err(error) => return Err(error.into()), + Err(error) => return Err(error), }; if user.iota_id.map(|id| id.0) != Some(iota_id) || !state.presence.has_iota_route(iota_id) { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) + .send_error_response(value.require_id()?, CommunicationType::ErrorNoIota) .await; } apply_preferences(&state, preferences); @@ -291,7 +256,7 @@ pub async fn user_connected( .track_session(user_id, session_id, omikron_id, iota_id); publish_changed_states(&state, &before, &users).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -309,7 +274,7 @@ pub async fn user_disconnected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let Some(session_id) = value @@ -319,7 +284,7 @@ pub async fn user_disconnected( .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; if let Ok(user) = user_repo::get_by_user_id(user_id.into()).await { @@ -337,137 +302,7 @@ pub async fn user_disconnected( .remove_session(user_id, session_id, omikron_id); } connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) - .await -} - -pub async fn set_user_state( - state: Arc, - connection: Arc, - value: CommunicationValue, - omikron_id: i64, -) -> OmikronResult<()> { - let Some(user_id) = i64::try_from(value.get_sender()).ok().filter(|id| *id > 0) else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoUserId) - .await; - }; - if let Some(requested_user) = value.get_data(DataType::UserId) { - let Some(requested_user_id) = requested_user - .as_number() - .and_then(|id| i64::try_from(id).ok()) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "user_id", - ) - .await; - }; - if requested_user_id != user_id { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "user_id", - ) - .await; - } - } - let Some(iota_id) = value - .get_data(DataType::IotaId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "iota_id", - ) - .await; - }; - let Some(requested_state) = value - .get_data(DataType::UserState) - .as_str() - .and_then(UserStatus::from_client_preference) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "user_state", - ) - .await; - }; - if !state.presence.has_iota_route(iota_id) { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) - .await; - } - let Some(session_id) = value - .get_data(DataType::SessionId) - .as_number() - .and_then(|id| i64::try_from(id).ok()) - .filter(|id| *id > 0) - else { - return connection - .send_error_response_with_detail( - value.get_id(), - CommunicationType::ErrorInvalidData, - "session_id", - ) - .await; - }; - let Some(route) = state.presence.session_route(user_id, session_id) else { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) - .await; - }; - if route.omikron_id != omikron_id || route.iota_id != iota_id { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) - .await; - } - if !state.presence.has_active_session_for_iota(user_id, iota_id) { - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNoIota) - .await; - } - let previous_preference = state.presence.preference(user_id); - let previous_state = state.presence.resolve_public_state(user_id, iota_id); - if let Err(error) = - user_repo::change_presence_preference(user_id.into(), requested_state.to_string()).await - { - log_in!( - crate::util::logger::PrintType::General, - "Failed to persist presence preference: {}", - error - ); - return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInternal) - .await; - } - state - .presence - .set_preference(user_id, requested_state.clone()); - let new_state = state.presence.resolve_public_state(user_id, iota_id); - if requested_state != previous_preference { - publish_private_state(&state, user_id, &requested_state).await; - } - if requested_state != previous_preference && new_state != previous_state { - publish_state_changes(&state, &[(user_id, new_state)]).await; - } - connection - .send( - &CommunicationValue::new(CommunicationType::Success) - .with_id(value.get_id()) - .add_typed_default( - DataType::UserState, - DataValue::Str(requested_state.to_string()), - ), - ) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -481,10 +316,11 @@ pub async fn iota_connected( let Some(iota_id) = value .get_data(DataType::IotaId) .as_number() - .map(|id| id as i64) + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; @@ -503,7 +339,7 @@ pub async fn iota_connected( crate::transport::omikron_manager::deliver_pending_erasures(iota_id).await; publish_changed_states(&state, &before, &users).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -517,10 +353,11 @@ pub async fn iota_disconnected( let Some(iota_id) = value .get_data(DataType::IotaId) .as_number() - .map(|id| id as i64) + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?; @@ -530,7 +367,7 @@ pub async fn iota_disconnected( state.presence.untrack_iota_connection(iota_id, omikron_id); publish_changed_states(&state, &before, &users).await; connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?)) .await } @@ -540,7 +377,7 @@ pub async fn sync_status( value: CommunicationValue, omikron_id: i64, ) -> OmikronResult<()> { - let request_id = value.get_id(); + let request_id = value.require_id()?; let Some(DataValue::Array(iota_values)) = value.get_data(DataType::IotaIds) else { return connection .send_error_response(request_id, CommunicationType::ErrorInvalidData) diff --git a/src/transport/handlers/register.rs b/src/transport/handlers/register.rs index 8799893..2c2501a 100644 --- a/src/transport/handlers/register.rs +++ b/src/transport/handlers/register.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::{iota_repo, user_repo}, @@ -22,13 +22,13 @@ pub async fn get_register( .filter(|id| user_repo::valid_protocol_id(*id)); let Some(iota_id) = iota_id else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let (register_id, registration_token) = - user_repo::allocate_registration(IotaId::from(iota_id), value.get_id()).await?; + user_repo::allocate_registration(IotaId::from(iota_id), value.require_id()?).await?; let response = CommunicationValue::new(CommunicationType::GetRegister) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default( DataType::UserId, DataValue::SignedNumber(register_id.0.into()), @@ -47,7 +47,7 @@ pub async fn complete_iota( .and_then(|key| PublicKeyBundle::from_base64(key).ok()); let Some(public_key) = public_key else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; match iota_repo::create_new_iota(public_key).await { @@ -55,7 +55,7 @@ pub async fn complete_iota( connection .send( &CommunicationValue::new(CommunicationType::CompleteRegisterIota) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.0.into())), ) .await @@ -64,7 +64,7 @@ pub async fn complete_iota( connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await @@ -108,7 +108,7 @@ pub async fn complete_user( }) else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; // Omikron supplies the authenticated Iota ID in the payload. The lease @@ -120,7 +120,7 @@ pub async fn complete_user( .filter(|id| user_repo::valid_protocol_id(*id)); let Some(iota_id) = iota_id else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; @@ -136,14 +136,17 @@ pub async fn complete_user( { Ok(()) => { connection - .send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())) + .send( + &CommunicationValue::new(CommunicationType::Success) + .with_id(value.require_id()?), + ) .await } Err(error) => { connection .send( &CommunicationValue::new(CommunicationType::ErrorInternal) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), ) .await diff --git a/src/transport/handlers/states.rs b/src/transport/handlers/states.rs index e1fc12d..0b46254 100644 --- a/src/transport/handlers/states.rs +++ b/src/transport/handlers/states.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::db::user_repo; use mtp::{ @@ -34,7 +34,7 @@ pub async fn get( let Some(DataValue::Array(ids)) = value.get_data(DataType::UserIds) else { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, None, ) @@ -47,7 +47,7 @@ pub async fn get( if session_id.is_none() && !legacy_peer { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, None, ) @@ -60,7 +60,7 @@ pub async fn get( let DataValue::SignedNumber(id) = id else { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, session_id, ) @@ -69,7 +69,7 @@ pub async fn get( let Ok(user_id) = i64::try_from(*id) else { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, session_id, ) @@ -78,7 +78,7 @@ pub async fn get( if user_id <= 0 { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInvalidData, session_id, ) @@ -95,7 +95,7 @@ pub async fn get( Err(_) => { return send_error( connection, - value.get_id(), + value.require_id()?, CommunicationType::ErrorInternal, session_id, ) @@ -110,9 +110,10 @@ pub async fn get( missing_user_ids.push(user_id); continue; }; - let status = state - .presence - .resolve_public_state(user_id, user.iota_id.map(|id| id.0).unwrap_or_default()) + let status = user + .iota_id + .map(|iota_id| state.presence.resolve_public_state(user_id, iota_id.0)) + .unwrap_or(crate::sql::connection_status::UserStatus::user_offline) .to_string(); let mut map = Vec::new(); if let Some(kind) = DataType::UserId.try_to_id(&tm) { @@ -124,7 +125,7 @@ pub async fn get( states.push(DataValue::Container(map)); } let response = CommunicationValue::new(CommunicationType::GetStates) - .with_id(value.get_id()) + .with_id(value.require_id()?) .add_typed_default(DataType::UserStates, DataValue::Array(states)); let response = if let Some(session_id) = session_id { response.add_typed_default(DataType::SessionId, DataValue::SignedNumber(session_id)) diff --git a/src/transport/handlers/user_data.rs b/src/transport/handlers/user_data.rs index 1c248fa..e6b676a 100644 --- a/src/transport/handlers/user_data.rs +++ b/src/transport/handlers/user_data.rs @@ -1,5 +1,5 @@ use super::super::connection::{ - MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat, + OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields, }; use crate::{ db::{iota_repo, user_repo}, @@ -29,11 +29,16 @@ pub async fn get_user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let request_id = value.require_id()?; + let sender = value.require_sender_i64()?; let state = connection.state(); - let user = if let Some(id) = value.get_data(DataType::UserId).as_number() { - user_repo::get_by_user_id(UserId::from(id as i64)) - .await - .ok() + let user = if let Some(id) = value + .get_data(DataType::UserId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + user_repo::get_by_user_id(UserId::from(id)).await.ok() } else if let Some(name) = value.get_data(DataType::Username).as_str() { user_repo::get_by_username(name).await.ok() } else { @@ -41,7 +46,7 @@ pub async fn get_user( }; let Some(user) = user else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(request_id, CommunicationType::ErrorNotFound) .await; }; let id = user.id.0; @@ -52,11 +57,11 @@ pub async fn get_user( .filter(|name| !name.is_empty()) .unwrap_or_else(|| username.clone()); let mut response = CommunicationValue::new(CommunicationType::GetUserData) - .with_id(value.get_id()) + .with_id(request_id) .add_typed_default(DataType::Username, DataValue::Str(username)) .add_typed_default( DataType::PublicKey, - DataValue::Str(user.public_key.to_base64()), + DataValue::Str(user.public_key.try_to_base64()?), ) .add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::Display, DataValue::Str(display)) @@ -79,7 +84,7 @@ pub async fn get_user( response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar))); } let route = state.presence.user_route(id); - let private_request = value.get_sender() as i64 == id; + let private_request = sender == id; let resolved_status = if private_request { if !state .presence @@ -125,13 +130,21 @@ pub async fn get_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let found = if let Some(id) = value.get_data(DataType::IotaId).as_number() { - iota_repo::get_iota_by_id(IotaId::from(id as i64)) + let request_id = value.require_id()?; + let found = if let Some(id) = value + .get_data(DataType::IotaId) + .as_number() + .and_then(|id| i64::try_from(id).ok()) + .filter(|id| *id > 0) + { + iota_repo::get_iota_by_id(IotaId::from(id)) .await .ok() .map(|iota| (iota.id.0, iota.public_key, None, None)) } else if let Some(id) = value.get_data(DataType::UserId).as_number() { - if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await { + if let Some(id) = i64::try_from(id).ok().filter(|id| *id > 0) + && let Ok(user) = user_repo::get_by_user_id(UserId::from(id)).await + { match user.iota_id { Some(iota_id) => iota_repo::get_iota_by_id(iota_id) .await @@ -163,12 +176,12 @@ pub async fn get_iota( }; let Some((id, key, user_id, username)) = found else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(request_id, CommunicationType::ErrorNotFound) .await; }; let mut response = CommunicationValue::new(CommunicationType::GetIotaData) - .with_id(value.get_id()) - .add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64())) + .with_id(request_id) + .add_typed_default(DataType::PublicKey, DataValue::Str(key.try_to_base64()?)) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())) .add_typed_default(DataType::OmikronConnections, connections(&connection, id)); if let Some(user_id) = user_id { @@ -185,7 +198,8 @@ async fn update_user( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { - let id = UserId::from(value.get_sender() as i64); + let request_id = value.require_id()?; + let id = UserId::from(value.require_sender_i64()?); let mut error = None; if let Some(name) = value.get_data(DataType::Username).as_str() { error = user_repo::change_username(id, name.to_owned()) @@ -193,56 +207,55 @@ async fn update_user( .err() .map(|e| e.to_string()); } - if error.is_none() { - if let Some(name) = value.get_data(DataType::Display).as_str() { - error = user_repo::change_display_name(id, name.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(name) = value.get_data(DataType::Display).as_str() + { + error = user_repo::change_display_name(id, name.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(avatar) = value.get_data(DataType::Avatar).as_str() { - error = user_repo::change_avatar(id, avatar.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(avatar) = value.get_data(DataType::Avatar).as_str() + { + error = user_repo::change_avatar(id, avatar.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(about) = value.get_data(DataType::About).as_str() { - error = user_repo::change_about(id, about.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(about) = value.get_data(DataType::About).as_str() + { + error = user_repo::change_about(id, about.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(status) = value.get_data(DataType::Status).as_str() { - error = user_repo::change_status(id, status.to_owned()) - .await - .err() - .map(|e| e.to_string()); - } + if error.is_none() + && let Some(status) = value.get_data(DataType::Status).as_str() + { + error = user_repo::change_status(id, status.to_owned()) + .await + .err() + .map(|e| e.to_string()); } - if error.is_none() { - if let Some(key) = value + if error.is_none() + && let Some(key) = value .get_data(DataType::PublicKey) .as_str() .and_then(|key| PublicKeyBundle::from_base64(key).ok()) - { - error = user_repo::change_keys(id, key) - .await - .err() - .map(|e| e.to_string()); - } + { + error = user_repo::change_keys(id, key) + .await + .err() + .map(|e| e.to_string()); } let response = match error { None => CommunicationValue::new(CommunicationType::Success), Some(error) => CommunicationValue::new(CommunicationType::ErrorInternal) .add_typed_default(DataType::ErrorType, DataValue::Str(error)), }; - connection.send(&response.with_id(value.get_id())).await + connection.send(&response.with_id(request_id)).await } pub async fn change_user( @@ -256,34 +269,35 @@ pub async fn change_iota( connection: Arc, value: CommunicationValue, ) -> OmikronResult<()> { + let request_id = value.require_id()?; let reset_data = value.get_data(DataType::ResetToken); let Some(reset) = reset_data.as_str() else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; let new_token_data = value.get_data(DataType::NewToken); let Some(new_token) = new_token_data.as_str() else { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidData) + .send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData) .await; }; - let user_id = UserId::from(value.get_sender() as i64); + let user_id = UserId::from(value.require_sender_i64()?); let user = match user_repo::get_by_user_id(user_id).await { Ok(user) => user, Err(_) => { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorNotFound) + .send_error_response(request_id, CommunicationType::ErrorNotFound) .await; } }; if user.token != reset { return connection - .send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge) + .send_error_response(request_id, CommunicationType::ErrorInvalidChallenge) .await; } let result = - match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))) + match user_repo::change_iota_id(user_id, Some(IotaId::from(value.require_sender_i64()?))) .await { Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await, @@ -294,5 +308,5 @@ pub async fn change_iota( Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal) .add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())), }; - connection.send(&response.with_id(value.get_id())).await + connection.send(&response.with_id(request_id)).await } diff --git a/src/transport/omikron_connection.rs b/src/transport/omikron_connection.rs index 82b9ece..83a9df7 100644 --- a/src/transport/omikron_connection.rs +++ b/src/transport/omikron_connection.rs @@ -1,12 +1,13 @@ use super::capabilities::{OmegaCapabilities, PeerCapabilities}; use crate::models::OmikronId; use crate::{ - load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server, + log, log_cv_in, log_cv_out, log_err, log_in, server, state::OmegaState, + transport::connection::{RequiredMtpFields, validate_dispatch_fields}, transport::omikron_manager, util::{file_util::load_file_vec, logger::PrintType}, }; -use dashmap::DashMap; +use dashmap::{DashMap, mapref::entry::Entry}; use mtp::{ codec::{CommunicationType, CommunicationValue}, crypto::PublicKeyBundle, @@ -16,19 +17,21 @@ use mtp::{ use std::{ net::{IpAddr, Ipv4Addr}, sync::{ - Arc, - atomic::{AtomicUsize, Ordering}, + Arc, Mutex as StdMutex, + atomic::{AtomicU32, AtomicUsize, Ordering}, }, time::{Duration, Instant}, }; use tokio::{ - sync::{Mutex, mpsc}, + sync::{Mutex, Semaphore, mpsc, oneshot}, time::interval, }; +use tokio_util::{sync::CancellationToken, task::TaskTracker}; const CLEANUP_INTERVAL: Duration = Duration::from_secs(30); const MAX_WAITING_AGE: Duration = Duration::from_secs(60); static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0); +static NEXT_CORRELATION_ID: AtomicU32 = AtomicU32::new(1); static ACTIVE_CONNECTIONS_BY_IP: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(DashMap::new); @@ -47,16 +50,27 @@ impl Drop for ConnectionLimitGuard { pub type OmikronResult = crate::error::Result; pub struct WaitingTask { - pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub sender: oneshot::Sender, pub inserted_at: Instant, } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum DispatchClass { + Concurrent, + Ordered, +} + pub struct OmikronConnection { id: u64, state: Arc, sender: Mutex>, waiting_tasks: DashMap, cleanup_handle: std::sync::Mutex>>, + handler_tasks: TaskTracker, + handler_cancel: CancellationToken, + handler_limit: Arc, + ordered_sender: mpsc::Sender, + ordered_receiver: StdMutex>>, peer_capabilities: PeerCapabilities, } impl Drop for OmikronConnection { @@ -64,6 +78,9 @@ impl Drop for OmikronConnection { if let Some(handle) = self.cleanup_handle.lock().unwrap().take() { handle.abort(); } + self.handler_cancel.cancel(); + self.handler_tasks.close(); + self.waiting_tasks.clear(); } } @@ -75,17 +92,24 @@ impl OmikronConnection { state: Arc, authenticated: bool, ) -> Option> { - if !authenticated { + if !authenticated || id == 0 || i64::try_from(id).is_err() { return None; } let peer_capabilities = PeerCapabilities::from_identification_description(description).ok()?; + let handler_concurrency = state.omikron_handler_concurrency; + let (ordered_sender, ordered_receiver) = mpsc::channel(handler_concurrency); Some(Arc::new(Self { id, state, sender: Mutex::new(Some(sender)), waiting_tasks: DashMap::new(), cleanup_handle: std::sync::Mutex::new(None), + handler_tasks: TaskTracker::new(), + handler_cancel: CancellationToken::new(), + handler_limit: Arc::new(Semaphore::new(handler_concurrency)), + ordered_sender, + ordered_receiver: StdMutex::new(Some(ordered_receiver)), peer_capabilities, })) } @@ -94,6 +118,115 @@ impl OmikronConnection { &self.peer_capabilities } + fn track_handler(&self, task: F) + where + F: std::future::Future + Send + 'static, + { + let handle = self.handler_tasks.spawn(task); + let id = self.id as i64; + tokio::spawn(async move { + if let Err(error) = handle.await { + log_err!( + id, + PrintType::Omega, + "Omikron handler task failed: {}", + error + ); + } + }); + } + + fn start_ordered_worker(self: &Arc) { + let Some(mut receiver) = self.ordered_receiver.lock().unwrap().take() else { + return; + }; + let connection = self.clone(); + let cancellation = self.handler_cancel.clone(); + self.track_handler(async move { + loop { + let value = tokio::select! { + _ = cancellation.cancelled() => break, + value = receiver.recv() => match value { + Some(value) => value, + None => break, + }, + }; + let global_permit = tokio::select! { + _ = cancellation.cancelled() => break, + permit = connection.state.global_handler_limit.clone().acquire_owned() => { + match permit { + Ok(permit) => permit, + Err(_) => break, + } + } + }; + let permit = tokio::select! { + _ = cancellation.cancelled() => break, + permit = connection.handler_limit.clone().acquire_owned() => { + match permit { + Ok(permit) => permit, + Err(_) => break, + } + } + }; + let result = tokio::select! { + _ = cancellation.cancelled() => break, + result = connection.clone().dispatch(value) => result, + }; + drop(permit); + drop(global_permit); + if let Err(error) = result { + log_err!( + connection.id as i64, + PrintType::Omega, + "Error processing ordered Omikron message: {}", + error + ); + } + } + }); + } + + async fn stop_handlers(&self) { + self.handler_cancel.cancel(); + self.handler_tasks.close(); + let _ = tokio::time::timeout(Duration::from_secs(1), self.handler_tasks.wait()).await; + self.waiting_tasks.clear(); + } + + fn correlation_response(value: &CommunicationValue) -> bool { + matches!( + value.get_comm_type_enum(), + Some(CommunicationType::Success) + | Some(CommunicationType::ErrorProtocol) + | Some(CommunicationType::ErrorAnonymous) + | Some(CommunicationType::ErrorInternal) + | Some(CommunicationType::ErrorInvalidData) + | Some(CommunicationType::ErrorInvalidUserId) + | Some(CommunicationType::ErrorInvalidOmikronId) + | Some(CommunicationType::ErrorNotFound) + | Some(CommunicationType::ErrorNotAuthenticated) + | Some(CommunicationType::ErrorNoIota) + | Some(CommunicationType::ErrorInvalidChallenge) + | Some(CommunicationType::ErrorInvalidSecret) + | Some(CommunicationType::ErrorInvalidPrivateKey) + | Some(CommunicationType::ErrorInvalidPublicKey) + | Some(CommunicationType::ErrorNoUserId) + | Some(CommunicationType::ErrorNoCallId) + | Some(CommunicationType::ErrorInvalidCallId) + ) + } + + fn next_correlation_id(&self) -> Option { + for _ in 0..u32::MAX { + let id = NEXT_CORRELATION_ID.fetch_add(1, Ordering::Relaxed); + if id != 0 && !self.waiting_tasks.contains_key(&id) { + return Some(id); + } + } + None + } + pub async fn handle(self: Arc, receiver: &mut WebMtpReceiver) { log_in!( self.id as i64, @@ -117,12 +250,16 @@ impl OmikronConnection { self.clone().cleanup().await; return; } - let cleanup_conn = self.clone(); + self.start_ordered_worker(); + let cleanup_conn = Arc::downgrade(&self); *self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move { let mut ticker = interval(CLEANUP_INTERVAL); loop { ticker.tick().await; - cleanup_conn + let Some(connection) = cleanup_conn.upgrade() else { + break; + }; + connection .waiting_tasks .retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE); } @@ -164,16 +301,81 @@ impl OmikronConnection { async fn process_message(self: Arc, value: CommunicationValue) -> OmikronResult<()> { log_cv_in!(PrintType::Omikron, &value); - if value.is_type(CommunicationType::Relay) { - return self.dispatch(value).await; - } - if let Some(message_id) = value.id() + if Self::correlation_response(&value) + && let Some(message_id) = value.id() && let Some((_, task)) = self.waiting_tasks.remove(&message_id) { - let _ = (task.task)(self.clone(), value); + let _ = task.sender.send(value); return Ok(()); } - self.dispatch(value).await + + if !value.is_type(CommunicationType::Relay) { + validate_dispatch_fields(&value)?; + } + + let dispatch_class = Self::dispatch_class(&value); + if dispatch_class == DispatchClass::Ordered { + tokio::select! { + _ = self.handler_cancel.cancelled() => { + return Err(crate::error::OmegaError::NotConnected); + } + result = self.ordered_sender.send(value) => { + result.map_err(|_| crate::error::OmegaError::NotConnected)?; + } + } + return Ok(()); + } + + let global_permit = tokio::select! { + _ = self.handler_cancel.cancelled() => { + return Err(crate::error::OmegaError::NotConnected); + } + permit = self.state.global_handler_limit.clone().acquire_owned() => { + permit.map_err(|_| crate::error::OmegaError::NotConnected)? + } + }; + let permit = tokio::select! { + _ = self.handler_cancel.cancelled() => { + drop(global_permit); + return Err(crate::error::OmegaError::NotConnected); + } + permit = self.handler_limit.clone().acquire_owned() => { + permit.map_err(|_| crate::error::OmegaError::NotConnected)? + } + }; + let connection = self.clone(); + let cancellation = self.handler_cancel.clone(); + self.track_handler(async move { + tokio::select! { + _ = cancellation.cancelled() => {} + result = connection.clone().dispatch(value) => { + if let Err(error) = result { + log_err!( + connection.id as i64, + PrintType::Omega, + "Error processing Omikron message: {}", + error + ); + } + } + } + drop(permit); + drop(global_permit); + }); + Ok(()) + } + + fn dispatch_class(value: &CommunicationValue) -> DispatchClass { + match value.get_comm_type_enum() { + Some(CommunicationType::UserConnected) + | Some(CommunicationType::UserDisconnected) + | Some(CommunicationType::IotaConnected) + | Some(CommunicationType::IotaDisconnected) + | Some(CommunicationType::SyncClientIotaStatus) + | Some(CommunicationType::StateSubscribe) + | Some(CommunicationType::ClientChanged) => DispatchClass::Ordered, + _ => DispatchClass::Concurrent, + } } async fn dispatch(self: Arc, value: CommunicationValue) -> OmikronResult<()> { @@ -341,33 +543,39 @@ impl OmikronConnection { value: &CommunicationValue, timeout: Duration, ) -> OmikronResult { - let (tx, mut rx) = mpsc::channel(1); - let message_id = value.id().unwrap_or_default(); - let task_tx = tx.clone(); - self.waiting_tasks.insert( - message_id, - WaitingTask { - task: Box::new(move |_, response| { - let task_tx = task_tx.clone(); - tokio::spawn(async move { - let _ = task_tx.send(response).await; - }); - true - }), - inserted_at: Instant::now(), - }, - ); + value.require_id()?; + let (tx, rx) = oneshot::channel(); + let message_id = self.next_correlation_id().ok_or_else(|| { + crate::OmegaError::Transport("no relay correlation id available".into()) + })?; + match self.waiting_tasks.entry(message_id) { + Entry::Vacant(entry) => { + entry.insert(WaitingTask { + sender: tx, + inserted_at: Instant::now(), + }); + } + Entry::Occupied(_) => { + return Err(crate::OmegaError::Transport( + "relay correlation id collision".into(), + )); + } + } + let outbound = value.clone().with_id(message_id); - if let Err(error) = self.clone().send(value).await { + if let Err(error) = self.clone().send(&outbound).await { self.waiting_tasks.remove(&message_id); return Err(error); } - match tokio::time::timeout(timeout, rx.recv()).await { - Ok(Some(response)) => Ok(response), - Ok(None) => Err(crate::error::OmegaError::Transport( - "Relay response channel closed".into(), - )), + match tokio::time::timeout(timeout, rx).await { + Ok(Ok(response)) => Ok(response), + Ok(Err(_)) => { + self.waiting_tasks.remove(&message_id); + Err(crate::error::OmegaError::Transport( + "Relay response channel closed".into(), + )) + } Err(_) => { self.waiting_tasks.remove(&message_id); Err(crate::error::OmegaError::Transport( @@ -411,6 +619,7 @@ impl OmikronConnection { } } async fn cleanup(self: Arc) { + self.stop_handlers().await; if self.id != 0 { log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected"); if omikron_manager::remove_omikron(self.id as i64, &self).await { @@ -441,7 +650,8 @@ pub async fn get_by_omikron_id( description: Option, ) -> Option { PeerCapabilities::from_identification_description(description.as_deref()).ok()?; - crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64)) + let omikron_id = i64::try_from(omikron_id).ok().filter(|id| *id > 0)?; + crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id)) .await .ok() .map(|omikron| omikron.public_key) @@ -453,7 +663,7 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option) -> u64 { pub async fn start(port: u16, state: Arc) -> Result<(), Box> { let cert_pem = load_file_vec("certs", "cert.pem")?; let key_pem = load_file_vec("certs", "key.pem")?; - let web_config = server::server::build_web_config()? + let web_config = server::web::build_web_config(state.identity.clone())? .serve_tcp_https(true) .max_tcp_connections(256); let ip = IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)); @@ -478,7 +688,7 @@ pub async fn start(port: u16, state: Arc) -> Result<(), Box) -> Result<(), Box) -> Result<(), Box config.transport_connections || peer_active.is_some_and(|count| count > config.transport_connections_per_ip) { - drop(ConnectionLimitGuard(peer_ip)); + drop(connection_limit_guard); log_err!( 0, PrintType::Omega, @@ -526,6 +737,7 @@ pub async fn start(port: u16, state: Arc) -> Result<(), Box) -> Result<(), Box>> = - Lazy::new(|| DashMap::new()); + Lazy::new(DashMap::new); pub async fn add_omikron(conn: Arc) { let id = match conn.clone().get_omikron_id().await { @@ -64,12 +64,11 @@ pub async fn get_all_connections() .map_err(|_| ())?; for user in users { for route in state.presence.routes_for_user(user.id.0) { - if let Some(iotas) = result.get_mut(&route.omikron_id) { - if let Some(iota_id) = user.iota_id - && let Some(users) = iotas.get_mut(&iota_id.0) - { - users.push(user.id.0); - } + if let Some(iotas) = result.get_mut(&route.omikron_id) + && let Some(iota_id) = user.iota_id + && let Some(users) = iotas.get_mut(&iota_id.0) + { + users.push(user.id.0); } } } @@ -97,10 +96,10 @@ pub async fn send_state_batch( pub async fn get_random_omikron() -> Result, ()> { let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect(); - if let Some(key) = keys.into_iter().choose(&mut rand::rng()) { - if let Some(connection) = get_connected_omikron(key) { - return Ok(connection); - } + if let Some(key) = keys.into_iter().choose(&mut rand::rng()) + && let Some(connection) = get_connected_omikron(key) + { + return Ok(connection); } Err(()) diff --git a/src/transport/relay_router.rs b/src/transport/relay_router.rs index 3d76705..0cbd1f2 100644 --- a/src/transport/relay_router.rs +++ b/src/transport/relay_router.rs @@ -21,14 +21,6 @@ pub enum RouteTarget { } impl RouteTarget { - pub fn wire_id(self) -> Option { - let (kind, id) = match self { - Self::User(id) => (USER_TARGET_KIND, id), - Self::Iota(id) => (IOTA_TARGET_KIND, id), - }; - (id > 0 && id <= TARGET_ID_MASK).then_some(kind | id) - } - pub fn from_wire_id(value: u64) -> Option { let id = value & TARGET_ID_MASK; if id == 0 || value & !(TARGET_KIND_MASK | TARGET_ID_MASK) != 0 { @@ -40,12 +32,6 @@ impl RouteTarget { _ => None, } } - - pub const fn id(self) -> u64 { - match self { - Self::User(id) | Self::Iota(id) => id, - } - } } #[derive(Debug, Error)] @@ -183,10 +169,11 @@ mod tests { use mtp::codec::DataValue; fn wire(target: RouteTarget) -> u64 { - let Some(value) = target.wire_id() else { - panic!("valid route target was rejected"); + let (kind, id) = match target { + RouteTarget::User(id) => (USER_TARGET_KIND, id), + RouteTarget::Iota(id) => (IOTA_TARGET_KIND, id), }; - value + kind | id } fn relay_frame() -> CommunicationValue { diff --git a/src/util/file_util.rs b/src/util/file_util.rs index 8630489..05b9018 100644 --- a/src/util/file_util.rs +++ b/src/util/file_util.rs @@ -1,293 +1,15 @@ use once_cell::sync::Lazy; -use std::fs::{self, File}; -use std::io::{self, BufReader, Read}; -use std::path::{Path, PathBuf}; -use tokio::io::AsyncWriteExt; -use uuid::Uuid; -use zip::ZipArchive; - -use crate::log; +use std::path::PathBuf; static WORKING_DIR: Lazy = Lazy::new(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))); -pub fn delete_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file = dir.join(name); - if !file.exists() { - return false; - } - fs::remove_file(file).is_ok() -} - -#[allow(dead_code)] -pub fn delete_directory(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - delete_dir_recursive(&dir) -} - -#[allow(dead_code)] -fn delete_dir_recursive(directory: &Path) -> bool { - if !directory.exists() { - return false; - } - if let Err(e) = fs::remove_dir_all(directory) { - log!( - "[IMPORTANT] Couldn't delete directory {}: {}", - directory.display(), - e - ); - return false; - } - true -} - -#[allow(dead_code)] -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_buf(path: &str, name: &str) -> io::Result> { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - // Ensure the directory exists, create if necessary - if !dir.exists() { - if let Err(_) = fs::create_dir_all(&dir) { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "Directory creation failed", - )); - } - } - - // Create the file if it doesn't exist - if !file_path.exists() { - return Err(io::Error::new( - io::ErrorKind::NotFound, - "File creation failed", - )); - } - - // Open the file and return a BufReader for efficient reading - let file = File::open(&file_path)?; - Ok(BufReader::new(file)) -} -pub fn has_file(path: &str, name: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - let file_path = dir.join(name); - - if !dir.exists() { - return false; - } - - if !file_path.exists() { - return false; - } - - true -} -pub fn has_dir(path: &str) -> bool { - let dir = Path::new(&get_directory()).join(path); - - if !dir.exists() { - return false; - } - - true -} - -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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return String::new(); - } - return String::new(); - } - - if !file_path.exists() { - if let Err(e) = File::create(&file_path) { - log!("[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 load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { - let dir = Path::new(&get_directory()).join(path); + let dir = PathBuf::from(get_directory()).join(path); let file_path = dir.join(name); - std::fs::read(file_path) } -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) { - log!("[IMPORTANT] Couldn't create directories: {}", e); - return; - } - } - - if let Err(e) = fs::write(&file_path, value) { - log!( - "[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 { WORKING_DIR.to_string_lossy().to_string() } - -pub fn working_dir() -> &'static Path { - &WORKING_DIR -} - -// Helper to download the zip file content to a file on disk -#[allow(dead_code)] -async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box> { - let response = reqwest::get(url).await?; - - // Check for successful response status - if !response.status().is_success() { - return Err(format!("Failed to download file: Status {}", response.status()).into()); - } - - let mut zip_file = tokio::fs::File::create(as_name).await?; - let body = response.bytes().await?; - zip_file.write_all(&body).await?; - - Ok(()) -} - -#[allow(dead_code, deprecated)] -fn extract_zip_contents_to_folder( - zip_path: &Path, - target_dir: &Path, -) -> Result<(), Box> { - let file = File::open(zip_path)?; - let mut archive = ZipArchive::new(file)?; - - let staging_dir = target_dir.with_extension("staging"); - - let _ = fs::remove_dir_all(&staging_dir); - fs::create_dir_all(&staging_dir)?; - - let mut first_item_name: Option = None; - - for i in 0..archive.len() { - let mut file = archive.by_index(i)?; - let entry_path = staging_dir.join(file.sanitized_name()); - - if i == 0 { - if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 { - first_item_name = Some(file.sanitized_name()); - } - } - - if file.name().ends_with('/') { - fs::create_dir_all(&entry_path)?; - } else { - if let Some(parent) = entry_path.parent() { - fs::create_dir_all(parent)?; - } - let mut out_file = File::create(entry_path)?; - io::copy(&mut file, &mut out_file)?; - } - } - - if let Some(root_path) = first_item_name { - let root_dir = staging_dir.join(&root_path); - - if root_dir.is_dir() { - let root_contents_count = fs::read_dir(&staging_dir)?.count(); - - if root_contents_count == 1 - || (root_contents_count > 1 && fs::metadata(&root_dir).is_ok()) - { - let _ = fs::remove_dir_all(target_dir); - fs::create_dir_all(target_dir)?; - - for entry in fs::read_dir(root_dir)? { - let entry = entry?; - let src = entry.path(); - let dest = target_dir.join(entry.file_name()); - - if let Err(_) = fs::rename(&src, &dest) { - if src.is_file() { - fs::copy(&src, &dest)?; - } else { - if entry.path().is_dir() { - fs::rename(&src, &dest)?; - } - } - } - } - - let _ = fs::remove_dir_all(&staging_dir); - return Ok(()); - } - } - } - - log!("Extracting directly (no single root folder detected)."); - let _ = fs::remove_dir_all(target_dir); - fs::rename(&staging_dir, target_dir)?; - - Ok(()) -} - -#[allow(dead_code)] -pub async fn download_and_extract_zip(url: &str, as_name: &str) { - let base_dir = PathBuf::from(get_directory()); - let zip_filename = format!("{}.zip", Uuid::new_v4()); - let zip_path = base_dir.join(&zip_filename); - let target_dir = base_dir.join(as_name); - - if let Err(e) = download_zip(url, &zip_path).await { - log!("Error downloading file: {}", e); - return; - } - - let zip_path_clone = zip_path.clone(); - let target_dir_clone = target_dir.clone(); - let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone); - if let Err(e) = extract_result { - log!("Panic during ZIP extraction: {}", e); - } - - if let Err(e) = tokio::fs::remove_file(&zip_path).await { - log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e); - } -} diff --git a/src/util/logger.rs b/src/util/logger.rs index 9bd211a..c9c9719 100644 --- a/src/util/logger.rs +++ b/src/util/logger.rs @@ -10,7 +10,6 @@ use std::{ use ansi_term::Color; use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version}; -use crate::transport::connection::MtpValueCompat; use crate::util::file_util::get_directory; static LOGGER: OnceLock> = OnceLock::new(); @@ -223,7 +222,7 @@ pub fn log_cv_internal( let formatted = format_cv(cv); log_internal( - Some(cv.get_sender() as i64), + cv.sender().and_then(|sender| i64::try_from(sender).ok()), print_type.unwrap_or(PrintType::General), prefix, false, @@ -234,14 +233,14 @@ pub fn log_cv_internal( pub fn format_cv(cv: &CommunicationValue) -> String { let mut parts = Vec::new(); - let sender = cv.get_sender(); - let receiver = cv.get_receiver(); + let sender = cv.sender().filter(|sender| *sender != 0); + let receiver = cv.receiver().filter(|receiver| *receiver != 0); - if sender > 0 && receiver > 0 { + if let (Some(sender), Some(receiver)) = (sender, receiver) { parts.push(format!("{} > {}", sender, receiver)); - } else if sender > 0 { + } else if let Some(sender) = sender { parts.push(format!("{}", sender)); - } else if receiver > 0 { + } else if let Some(receiver) = receiver { parts.push(format!("> {}", receiver)); } @@ -249,7 +248,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String { .get_comm_type_enum() .map(|kind| kind.to_string()) .unwrap_or_else(|| cv.get_type().to_string()); - parts.push(format!("{} (id={})", comm_type, cv.get_id())); + let id = cv + .id() + .map(|id| id.to_string()) + .unwrap_or_else(|| "missing".to_string()); + parts.push(format!("{} (id={})", comm_type, id)); let data = cv.data().unwrap_or(&[]); @@ -258,7 +261,7 @@ pub fn format_cv(cv: &CommunicationValue) -> String { Version(3, 0), ); - parts.push(format!("{}", formated_data)); + parts.push(formated_data.to_string()); parts.join(": ") } From 656b6acb01dc010d78926928b85e22af5ae94686 Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Thu, 20 Aug 2026 19:00:43 +0300 Subject: [PATCH 219/220] Update Rust crate sqlx to 0.9.0 --- Cargo.lock | 488 +++++++++-------------------------------------------- Cargo.toml | 2 +- 2 files changed, 78 insertions(+), 412 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a4b6e1..05c3bf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,12 +310,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-oid" version = "0.10.2" @@ -479,25 +473,14 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.7.0", - "zeroize", -] - [[package]] name = "der" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ - "const-oid 0.10.2", - "pem-rfc7468 1.0.0", + "const-oid", + "pem-rfc7468", "zeroize", ] @@ -528,7 +511,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] @@ -540,7 +522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", ] @@ -580,8 +562,8 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "pkcs8 0.11.0", - "signature 3.0.0", + "pkcs8", + "signature", ] [[package]] @@ -594,7 +576,7 @@ dependencies = [ "ed25519", "serde", "sha2 0.11.0", - "signature 3.0.0", + "signature", "subtle", "zeroize", ] @@ -626,13 +608,12 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -651,7 +632,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" dependencies = [ - "foldhash 0.2.0", + "foldhash", "libm", "portable-atomic", "siphasher", @@ -683,9 +664,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -698,12 +679,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -944,13 +919,13 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -961,11 +936,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -980,31 +955,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - [[package]] name = "hkdf" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ - "hmac 0.13.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", + "hmac", ] [[package]] @@ -1016,15 +973,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "httlib-huffman" version = "0.3.4" @@ -1344,9 +1292,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -1360,18 +1305,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.2", -] - [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1411,12 +1344,12 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -1448,14 +1381,14 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" dependencies = [ - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", "hybrid-array", "module-lattice", - "pkcs8 0.11.0", + "pkcs8", "shake", - "signature 3.0.0", + "signature", ] [[package]] @@ -1520,7 +1453,7 @@ dependencies = [ "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand", "tokio", ] @@ -1534,7 +1467,7 @@ dependencies = [ "mtp-common", "mtp-crypto", "mtp-type-map", - "rand 0.10.2", + "rand", "thiserror 2.0.20", ] @@ -1559,10 +1492,10 @@ dependencies = [ "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", - "hkdf 0.13.0", + "hkdf", "ml-dsa", "mlkem-tls", - "rand 0.10.2", + "rand", "rand_core 0.6.4", "rustls", "serde", @@ -1578,7 +1511,7 @@ version = "0.3.0" source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-crypto", - "rand 0.10.2", + "rand", "thiserror 2.0.20", "zeroize", ] @@ -1592,7 +1525,7 @@ dependencies = [ "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand", "thiserror 2.0.20", "tokio", "tracing", @@ -1608,7 +1541,7 @@ dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", - "rand 0.10.2", + "rand", "rcgen", "rustls", "rustls-native-certs", @@ -1676,22 +1609,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.7", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.2.2" @@ -1707,16 +1624,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1724,7 +1631,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1754,7 +1660,7 @@ dependencies = [ "http", "mtp", "once_cell", - "rand 0.10.2", + "rand", "rustls", "serde", "serde_json", @@ -1808,7 +1714,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -1834,15 +1740,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "pem-rfc7468" version = "1.0.0" @@ -1864,35 +1761,14 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.10", - "spki 0.7.3", -] - [[package]] name = "pkcs8" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.1", - "spki 0.8.0", + "der", + "spki", ] [[package]] @@ -1901,12 +1777,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "poly1305" version = "0.8.0" @@ -1939,15 +1809,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -1989,7 +1850,7 @@ dependencies = [ "fastbloom", "getrandom 0.4.3", "lru-slab", - "rand 0.10.2", + "rand", "rand_pcg", "ring", "rustc-hash", @@ -2032,17 +1893,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.10.2" @@ -2054,16 +1904,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[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_core" version = "0.6.4" @@ -2112,15 +1952,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c93da5bb2c5d4e6c0ef7abeead62c89169a0a4882bfb83ac892f2423aea2fe" -dependencies = [ - "bitflags", -] - [[package]] name = "ring" version = "0.17.14" @@ -2135,26 +1966,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "signature 2.2.0", - "spki 0.7.3", - "subtle", - "zeroize", -] - [[package]] name = "rustc-hash" version = "2.1.3" @@ -2364,18 +2175,6 @@ dependencies = [ "zmij", ] -[[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 = "serde_yaml" version = "0.9.34+deprecated" @@ -2391,13 +2190,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.7" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -2459,16 +2258,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "3.0.0" @@ -2535,16 +2324,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der 0.7.10", -] - [[package]] name = "spki" version = "0.8.0" @@ -2552,7 +2331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.1", + "der", ] [[package]] @@ -2563,9 +2342,9 @@ checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2576,12 +2355,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64 0.22.1", "bytes", + "cfg-if", "crc", "crossbeam-queue", "either", @@ -2590,12 +2370,11 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "serde", "serde_json", @@ -2610,9 +2389,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -2623,15 +2402,15 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck", "hex", - "once_cell", "proc-macro2", "quote", "serde", @@ -2642,57 +2421,42 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "thiserror 2.0.20", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64 0.22.1", "bitflags", "byteorder", "bytes", "crc", - "digest 0.10.7", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand 0.8.7", - "rsa", "serde", "sha1", - "sha2 0.10.9", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror 2.0.20", "tracing", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64 0.22.1", @@ -2705,18 +2469,16 @@ dependencies = [ "futures-core", "futures-util", "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "home", + "hkdf", + "hmac", "itoa", "log", "md-5", "memchr", - "once_cell", - "rand 0.8.7", + "rand", "serde", "serde_json", - "sha2 0.10.9", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", @@ -2727,12 +2489,13 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -2742,7 +2505,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror 2.0.20", "tracing", @@ -3114,12 +2876,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -3186,13 +2942,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "winapi" @@ -3231,22 +2983,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3258,67 +3001,34 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3331,48 +3041,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3485,26 +3171,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "zerofrom" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 395ad06..09a50a8 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ rustls = { version = "0.23.42", default-features = false, features = [ "aws-lc-rs", "prefer-post-quantum", ] } -sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } +sqlx = { version = "0.9.0", features = ["mysql", "runtime-tokio", "migrate"] } tokio = { version = "*", features = ["full"] } tokio-util = { version = "0.7.19", features = ["rt"] } uuid = { version = "1.24.0", features = ["v4", "v7"] } From 0cd08f8360c516fbaa5c9a56fad93b87a81ffee8 Mon Sep 17 00:00:00 2001 From: Rasensprenger Date: Thu, 20 Aug 2026 19:00:43 +0300 Subject: [PATCH 220/220] Update Rust crate sqlx to 0.9.0 --- Cargo.lock | 488 +++++++++-------------------------------------------- Cargo.toml | 2 +- 2 files changed, 78 insertions(+), 412 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a4b6e1..05c3bf8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,12 +310,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "const-oid" version = "0.10.2" @@ -479,25 +473,14 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" -[[package]] -name = "der" -version = "0.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" -dependencies = [ - "const-oid 0.9.6", - "pem-rfc7468 0.7.0", - "zeroize", -] - [[package]] name = "der" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" dependencies = [ - "const-oid 0.10.2", - "pem-rfc7468 1.0.0", + "const-oid", + "pem-rfc7468", "zeroize", ] @@ -528,7 +511,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid 0.9.6", "crypto-common 0.1.7", "subtle", ] @@ -540,7 +522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ "block-buffer 0.12.1", - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", ] @@ -580,8 +562,8 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a" dependencies = [ - "pkcs8 0.11.0", - "signature 3.0.0", + "pkcs8", + "signature", ] [[package]] @@ -594,7 +576,7 @@ dependencies = [ "ed25519", "serde", "sha2 0.11.0", - "signature 3.0.0", + "signature", "subtle", "zeroize", ] @@ -626,13 +608,12 @@ dependencies = [ [[package]] name = "etcetera" -version = "0.8.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96" dependencies = [ "cfg-if", - "home", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -651,7 +632,7 @@ version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef975e30683b2d965054bb0a836f8973857c4ebf6acf274fe46617cd285060d8" dependencies = [ - "foldhash 0.2.0", + "foldhash", "libm", "portable-atomic", "siphasher", @@ -683,9 +664,9 @@ checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flume" -version = "0.11.1" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be" dependencies = [ "futures-core", "futures-sink", @@ -698,12 +679,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - [[package]] name = "foldhash" version = "0.2.0" @@ -944,13 +919,13 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.5" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", - "foldhash 0.1.5", + "foldhash", ] [[package]] @@ -961,11 +936,11 @@ checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "hashlink" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +checksum = "824e001ac4f3012dd16a264bec811403a67ca9deb6c102fc5049b32c4574b35f" dependencies = [ - "hashbrown 0.15.5", + "hashbrown 0.16.1", ] [[package]] @@ -980,31 +955,13 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - [[package]] name = "hkdf" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" dependencies = [ - "hmac 0.13.0", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest 0.10.7", + "hmac", ] [[package]] @@ -1016,15 +973,6 @@ dependencies = [ "digest 0.11.3", ] -[[package]] -name = "home" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "httlib-huffman" version = "0.3.4" @@ -1344,9 +1292,6 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -dependencies = [ - "spin", -] [[package]] name = "libc" @@ -1360,18 +1305,6 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libredox" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" -dependencies = [ - "bitflags", - "libc", - "plain", - "redox_syscall 0.9.2", -] - [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1411,12 +1344,12 @@ checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "md-5" -version = "0.10.6" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -1448,14 +1381,14 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" dependencies = [ - "const-oid 0.10.2", + "const-oid", "crypto-common 0.2.2", "ctutils", "hybrid-array", "module-lattice", - "pkcs8 0.11.0", + "pkcs8", "shake", - "signature 3.0.0", + "signature", ] [[package]] @@ -1520,7 +1453,7 @@ dependencies = [ "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand", "tokio", ] @@ -1534,7 +1467,7 @@ dependencies = [ "mtp-common", "mtp-crypto", "mtp-type-map", - "rand 0.10.2", + "rand", "thiserror 2.0.20", ] @@ -1559,10 +1492,10 @@ dependencies = [ "chacha20poly1305", "ed25519-dalek", "getrandom 0.4.3", - "hkdf 0.13.0", + "hkdf", "ml-dsa", "mlkem-tls", - "rand 0.10.2", + "rand", "rand_core 0.6.4", "rustls", "serde", @@ -1578,7 +1511,7 @@ version = "0.3.0" source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a" dependencies = [ "mtp-crypto", - "rand 0.10.2", + "rand", "thiserror 2.0.20", "zeroize", ] @@ -1592,7 +1525,7 @@ dependencies = [ "mtp-common", "mtp-crypto", "mtp-transport", - "rand 0.10.2", + "rand", "thiserror 2.0.20", "tokio", "tracing", @@ -1608,7 +1541,7 @@ dependencies = [ "mtp-codec", "mtp-common", "mtp-crypto", - "rand 0.10.2", + "rand", "rcgen", "rustls", "rustls-native-certs", @@ -1676,22 +1609,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-bigint-dig" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" -dependencies = [ - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand 0.8.7", - "smallvec", - "zeroize", -] - [[package]] name = "num-conv" version = "0.2.2" @@ -1707,16 +1624,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1724,7 +1631,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", ] [[package]] @@ -1754,7 +1660,7 @@ dependencies = [ "http", "mtp", "once_cell", - "rand 0.10.2", + "rand", "rustls", "serde", "serde_json", @@ -1808,7 +1714,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.18", + "redox_syscall", "smallvec", "windows-link", ] @@ -1834,15 +1740,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "pem-rfc7468" version = "1.0.0" @@ -1864,35 +1761,14 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pkcs1" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" -dependencies = [ - "der 0.7.10", - "pkcs8 0.10.2", - "spki 0.7.3", -] - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der 0.7.10", - "spki 0.7.3", -] - [[package]] name = "pkcs8" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" dependencies = [ - "der 0.8.1", - "spki 0.8.0", + "der", + "spki", ] [[package]] @@ -1901,12 +1777,6 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" -[[package]] -name = "plain" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" - [[package]] name = "poly1305" version = "0.8.0" @@ -1939,15 +1809,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" version = "1.0.107" @@ -1989,7 +1850,7 @@ dependencies = [ "fastbloom", "getrandom 0.4.3", "lru-slab", - "rand 0.10.2", + "rand", "rand_pcg", "ring", "rustc-hash", @@ -2032,17 +1893,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.10.2" @@ -2054,16 +1904,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[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_core" version = "0.6.4" @@ -2112,15 +1952,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c93da5bb2c5d4e6c0ef7abeead62c89169a0a4882bfb83ac892f2423aea2fe" -dependencies = [ - "bitflags", -] - [[package]] name = "ring" version = "0.17.14" @@ -2135,26 +1966,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rsa" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" -dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8 0.10.2", - "rand_core 0.6.4", - "signature 2.2.0", - "spki 0.7.3", - "subtle", - "zeroize", -] - [[package]] name = "rustc-hash" version = "2.1.3" @@ -2364,18 +2175,6 @@ dependencies = [ "zmij", ] -[[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 = "serde_yaml" version = "0.9.34+deprecated" @@ -2391,13 +2190,13 @@ dependencies = [ [[package]] name = "sha1" -version = "0.10.7" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "cpufeatures 0.3.0", + "digest 0.11.3", ] [[package]] @@ -2459,16 +2258,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "signature" version = "3.0.0" @@ -2535,16 +2324,6 @@ dependencies = [ "lock_api", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der 0.7.10", -] - [[package]] name = "spki" version = "0.8.0" @@ -2552,7 +2331,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" dependencies = [ "base64ct", - "der 0.8.1", + "der", ] [[package]] @@ -2563,9 +2342,9 @@ checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" [[package]] name = "sqlx" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +checksum = "378620ccc25c62c89d8be1c819e76a88d59bdcc3304733330788948e619bfd71" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2576,12 +2355,13 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +checksum = "05b44e85bf579a8eeb4ceaa77a3a523baf2bf0e9bac7e40f405d537b5d2d5ccb" dependencies = [ "base64 0.22.1", "bytes", + "cfg-if", "crc", "crossbeam-queue", "either", @@ -2590,12 +2370,11 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.5", + "hashbrown 0.16.1", "hashlink", "indexmap", "log", "memchr", - "once_cell", "percent-encoding", "serde", "serde_json", @@ -2610,9 +2389,9 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +checksum = "bd2b84f2bc39a5705ef27ec785a11c934a41bbd4a24941e257927cddc26b60bf" dependencies = [ "proc-macro2", "quote", @@ -2623,15 +2402,15 @@ dependencies = [ [[package]] name = "sqlx-macros-core" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +checksum = "fb8d96de5fdc85a5c4ec813432b523ec637e80ba98f046555f75f7908ddac7c3" dependencies = [ + "cfg-if", "dotenvy", "either", "heck", "hex", - "once_cell", "proc-macro2", "quote", "serde", @@ -2642,57 +2421,42 @@ dependencies = [ "sqlx-postgres", "sqlx-sqlite", "syn 2.0.119", + "thiserror 2.0.20", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +checksum = "90b8020fe17c5f2c245bfa2505d7ef59c5604839527c740266ad2214acebea27" dependencies = [ - "atoi", - "base64 0.22.1", "bitflags", "byteorder", "bytes", "crc", - "digest 0.10.7", + "digest 0.11.3", "dotenvy", "either", - "futures-channel", "futures-core", - "futures-io", "futures-util", "generic-array", - "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "itoa", "log", - "md-5", - "memchr", - "once_cell", "percent-encoding", - "rand 0.8.7", - "rsa", "serde", "sha1", - "sha2 0.10.9", - "smallvec", + "sha2 0.11.0", "sqlx-core", - "stringprep", "thiserror 2.0.20", "tracing", - "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +checksum = "87a2bdd6e83f6b3ea525ca9fee568030508b58355a43d0b2c1674d5f79dcd65e" dependencies = [ "atoi", "base64 0.22.1", @@ -2705,18 +2469,16 @@ dependencies = [ "futures-core", "futures-util", "hex", - "hkdf 0.12.4", - "hmac 0.12.1", - "home", + "hkdf", + "hmac", "itoa", "log", "md-5", "memchr", - "once_cell", - "rand 0.8.7", + "rand", "serde", "serde_json", - "sha2 0.10.9", + "sha2 0.11.0", "smallvec", "sqlx-core", "stringprep", @@ -2727,12 +2489,13 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +checksum = "488e99c397a62007e4229aec669a179816339afc6d2620ca6fa420dbee2e982c" dependencies = [ "atoi", "flume", + "form_urlencoded", "futures-channel", "futures-core", "futures-executor", @@ -2742,7 +2505,6 @@ dependencies = [ "log", "percent-encoding", "serde", - "serde_urlencoded", "sqlx-core", "thiserror 2.0.20", "tracing", @@ -3114,12 +2876,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasite" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -3186,13 +2942,9 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.1" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" -dependencies = [ - "libredox", - "wasite", -] +checksum = "626c4bac6755d76ffc12cb01b2eac751db1996b9e0041de9aa02c8c211ddc82c" [[package]] name = "winapi" @@ -3231,22 +2983,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", + "windows-targets", ] [[package]] @@ -3258,67 +3001,34 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -3331,48 +3041,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[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.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -3485,26 +3171,6 @@ dependencies = [ "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.56" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "zerofrom" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 395ad06..09a50a8 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,7 @@ rustls = { version = "0.23.42", default-features = false, features = [ "aws-lc-rs", "prefer-post-quantum", ] } -sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] } +sqlx = { version = "0.9.0", features = ["mysql", "runtime-tokio", "migrate"] } tokio = { version = "*", features = ["full"] } tokio-util = { version = "0.7.19", features = ["rt"] } uuid = { version = "1.24.0", features = ["v4", "v7"] }