From 0d07ddf851832200d7b3547d88b56d1c8334ac1e Mon Sep 17 00:00:00 2001 From: Alex Emmet <111742636+Alex-Emmet@users.noreply.github.com> Date: Tue, 23 Sep 2025 20:26:48 +0000 Subject: [PATCH] Fixes Patches Creation & More Working with files / Paths fixed Websocket PingPongs (There is no data behind this just PingPong) Storing User Profiles correctly Added necessary dependencies and initial code support for: - X.509 certificate handling via x509/pkcs8 crates - Key generation and crypto operations via x448/sha2 - Structured hex encoding and base64 encoding --- Cargo.lock | 214 +++++++++++++++++++++++++++++- Cargo.toml | 9 +- src/auth/auth_connector.rs | 72 ++++++---- src/data/communication.rs | 213 ++++++++++++++++------------- src/gui/log_panel.rs | 0 src/gui/settings_panel.rs | 0 src/gui/user_panel.rs | 0 src/langu/language_creator.rs | 58 ++++++++ src/langu/language_manager.rs | 66 +++++++++ src/langu/mod.rs | 2 + src/main.rs | 102 +++++++++----- src/omikron/mod.rs | 3 +- src/omikron/omikron_connection.rs | 91 ++++++++----- src/omikron/ping_pong_task.rs | 116 ++++++++++++++++ src/users/contact.rs | 32 +++-- src/users/mod.rs | 4 +- src/users/user_community_util.rs | 9 +- src/users/user_manager.rs | 78 ++++++++--- src/users/user_profile.rs | 66 +++++---- src/users/user_profile_full.rs | 2 +- src/util/chats_util.rs | 6 +- src/util/config_util.rs | 12 ++ src/util/mod.rs | 4 +- 23 files changed, 890 insertions(+), 269 deletions(-) create mode 100644 src/gui/log_panel.rs create mode 100644 src/gui/settings_panel.rs create mode 100644 src/gui/user_panel.rs create mode 100644 src/langu/language_creator.rs create mode 100644 src/langu/language_manager.rs create mode 100644 src/langu/mod.rs create mode 100644 src/omikron/ping_pong_task.rs diff --git a/Cargo.lock b/Cargo.lock index 5fe45ac..b18750a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,20 +12,27 @@ dependencies = [ "cmake", "color-eyre", "crossterm 0.29.0", + "der", "futures-util", + "hex", "json", "once_cell", + "pkcs8", "rand", + "rand_core 0.6.4", "ratatui", "reqwest", "rustls", "serde", + "sha2", "sys-info", "sysinfo", "tokio", "tokio-tungstenite", "uuid", "walkdir", + "x448", + "x509", ] [[package]] @@ -49,6 +56,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -136,6 +152,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + [[package]] name = "bitflags" version = "2.9.4" @@ -200,6 +222,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.0", +] + [[package]] name = "cmake" version = "0.1.54" @@ -250,6 +285,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "convert_case" version = "0.7.1" @@ -259,6 +300,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie-factory" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9885fa71e26b8ab7855e2ec7cae6e9b380edff76cd052e07c683a0319d51b3a2" +dependencies = [ + "futures", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -378,6 +428,16 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.3" @@ -438,6 +498,17 @@ dependencies = [ "litrs", ] +[[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" @@ -485,6 +556,12 @@ 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.1" @@ -527,6 +604,21 @@ dependencies = [ "percent-encoding", ] +[[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" @@ -534,6 +626,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -542,6 +635,23 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + [[package]] name = "futures-macro" version = "0.3.31" @@ -571,10 +681,13 @@ 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", @@ -655,6 +768,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "http" version = "1.3.1" @@ -782,6 +901,30 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "2.0.0" @@ -1120,6 +1263,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "num_threads" version = "0.1.7" @@ -1260,6 +1412,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.32" @@ -1322,7 +1484,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -1332,9 +1494,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "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" @@ -1619,6 +1787,17 @@ dependencies = [ "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" @@ -1686,6 +1865,16 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2645,6 +2834,27 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +[[package]] +name = "x448" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80139292cd89162e90853c1474945705aeb6b18073e857a8155f186aece9e456" +dependencies = [ + "ed448-goldilocks", + "hex", + "rand_core 0.5.1", +] + +[[package]] +name = "x509" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3cec94c3999f31341553f358ef55f65fc031291a022cd42ec0ce7219560c76" +dependencies = [ + "chrono", + "cookie-factory", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 01b3a8a..fa20969 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,14 @@ bytes = "*" crossterm = "*" color-eyre = "*" sys-info = "*" -rand = "0.8.5" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } base64 = "0.22.1" once_cell = "1.21.3" reqwest = "0.12.23" +x448 = { version = "*" } +hex = "*" +x509 = "*" +sha2 = "*" +der = "*" +pkcs8 = { version = "*", features = ["alloc"] } diff --git a/src/auth/auth_connector.rs b/src/auth/auth_connector.rs index e3acc9f..21a677c 100644 --- a/src/auth/auth_connector.rs +++ b/src/auth/auth_connector.rs @@ -1,12 +1,13 @@ -use std::time::Duration; -use std::collections::HashMap; -use uuid::Uuid; -use reqwest::header::CONTENT_TYPE; -use json::JsonValue; -use reqwest::{Client, Response}; -use crate::users::user_profile::UserProfile; use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; - +use crate::users::user_profile::UserProfile; +use hex; +use json::JsonValue; +use reqwest::header::CONTENT_TYPE; +use reqwest::{Client, Response}; +use sha2::{Digest, Sha256}; +use std::collections::HashMap; +use std::time::Duration; +use uuid::Uuid; #[derive(Debug, Clone)] pub struct AuthUser { pub created_at: i64, @@ -32,23 +33,29 @@ impl AuthConnector { } pub async fn unregister_user(user_id: Uuid, reset_token: &str) -> Option { - let url = format!("https:/auth.tensamin.methanium.net//api/delete/{}/", user_id); + let url = format!("https:/auth.tensamin.methanium.net/api/delete/{}/", user_id); let client = Self::client(); let mut payload = JsonValue::new_object(); payload["reset_token"] = reset_token.into(); - let res = client.post(&url) + let res = client + .post(&url) .header(CONTENT_TYPE, "application/json") - .body(payload.dump()).send().await.ok()?; + .body(payload.dump()) + .send() + .await + .ok()?; let json = res.text().await.ok()?; let cv = CommunicationValue::from_json(&json); Option::from(cv.is_type(CommunicationType::Success)) - } pub async fn get_uuid(username: &str) -> Option { - let url = format!("https://auth.tensamin.methanium.net/api/get/uuid/{}/", username); + let url = format!( + "https://auth.tensamin.methanium.net/api/get/uuid/{}/", + username + ); let client = Self::client(); let res = client.get(&url).send().await.ok()?; let json = res.text().await.ok()?; @@ -71,15 +78,30 @@ impl AuthConnector { } Some(AuthUser { - created_at: cv.get_data(DataTypes::CreatedAt).unwrap().to_string().parse::().unwrap_or(-1), + created_at: cv + .get_data(DataTypes::CreatedAt) + .unwrap() + .to_string() + .parse::() + .unwrap_or(-1), username: cv.get_data(DataTypes::Username).unwrap().to_string(), display: cv.get_data(DataTypes::Display).unwrap().to_string(), avatar: cv.get_data(DataTypes::Avatar).unwrap().to_string(), about: cv.get_data(DataTypes::About).unwrap().to_string(), status: cv.get_data(DataTypes::Status).unwrap().to_string(), public_key: cv.get_data(DataTypes::PublicKey).unwrap().to_string(), - sub_level: cv.get_data(DataTypes::SubLevel).unwrap().to_string().parse::().unwrap_or(-1), - sub_end: cv.get_data(DataTypes::SubEnd).unwrap().to_string().parse::().unwrap_or(-1), + sub_level: cv + .get_data(DataTypes::SubLevel) + .unwrap() + .to_string() + .parse::() + .unwrap_or(-1), + sub_end: cv + .get_data(DataTypes::SubEnd) + .unwrap() + .to_string() + .parse::() + .unwrap_or(-1), }) } @@ -108,14 +130,16 @@ impl AuthConnector { let res = client .post(url) .header(CONTENT_TYPE, "application/json") - .body(payload.dump()) + .body(payload.to_string()) .send() - .await; + .await + .unwrap(); - match res { - Ok(resp) => Self::handle_response(resp).await, - Err(_) => false, - } + println!("Status: {}", res.status()); + let body = res.text().await.unwrap(); + println!("Response body:\n{}", body); + + CommunicationValue::from_json(&body).is_type(CommunicationType::Success) } pub async fn migrate_user(user_profile: &mut UserProfile, iota_id: &str) -> bool { @@ -152,6 +176,4 @@ impl AuthConnector { Err(_) => false, } } - - -} \ No newline at end of file +} diff --git a/src/data/communication.rs b/src/data/communication.rs index 79cf97d..98bdf50 100644 --- a/src/data/communication.rs +++ b/src/data/communication.rs @@ -1,15 +1,17 @@ +use axum::Json; +use json::number::Number; +use json::{JsonValue, object, parse, stringify}; use std::any::Any; -use json::{object, parse, stringify, JsonValue}; use std::collections::HashMap; +use std::env::VarsOs; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use axum::Json; use uuid::Uuid; #[derive(Eq, Hash, PartialEq, Debug)] pub enum DataTypes { - ErrorType, - ChatPartnerId, + ErrorType, + ChatPartnerId, IotaId, UserId, UserIds, @@ -81,71 +83,71 @@ impl DataTypes { let normalized = p0.to_lowercase().replace('_', ""); match normalized.as_str() { - "errortype" => DataTypes::ErrorType, - "chatpartnerid" => DataTypes::ChatPartnerId, - "iotaid" => DataTypes::IotaId, - "userid" => DataTypes::UserId, - "userids" => DataTypes::UserIds, - "userstate" => DataTypes::UserState, - "userstates" => DataTypes::UserStates, - "userpings" => DataTypes::UserPings, - "callstate" => DataTypes::CallState, - "screenshare" => DataTypes::ScreenShare, - "privatekeyhash" => DataTypes::PrivateKeyHash, - "accepted" => DataTypes::Accepted, + "errortype" => DataTypes::ErrorType, + "chatpartnerid" => DataTypes::ChatPartnerId, + "iotaid" => DataTypes::IotaId, + "userid" => DataTypes::UserId, + "userids" => DataTypes::UserIds, + "userstate" => DataTypes::UserState, + "userstates" => DataTypes::UserStates, + "userpings" => DataTypes::UserPings, + "callstate" => DataTypes::CallState, + "screenshare" => DataTypes::ScreenShare, + "privatekeyhash" => DataTypes::PrivateKeyHash, + "accepted" => DataTypes::Accepted, "acceptedprofiles" => DataTypes::AcceptedProfiles, - "deniedprofiles" => DataTypes::DeniedProfiles, - "messagecontent" => DataTypes::MessageContent, - "messagechunk" => DataTypes::MessageChunk, - "sendtime" => DataTypes::SendTime, - "gettime" => DataTypes::GetTime, - "getvariant" => DataTypes::GetVariant, - "sharedsecretown" => DataTypes::SharedSecretOwn, - "sharedsecretother"=> DataTypes::SharedSecretOther, + "deniedprofiles" => DataTypes::DeniedProfiles, + "messagecontent" => DataTypes::MessageContent, + "messagechunk" => DataTypes::MessageChunk, + "sendtime" => DataTypes::SendTime, + "gettime" => DataTypes::GetTime, + "getvariant" => DataTypes::GetVariant, + "sharedsecretown" => DataTypes::SharedSecretOwn, + "sharedsecretother" => DataTypes::SharedSecretOther, "sharedsecretsign" => DataTypes::SharedSecretSign, - "sharedsecret" => DataTypes::SharedSecret, - "callid" => DataTypes::CallId, - "callname" => DataTypes::CallName, - "callsecretsha" => DataTypes::CallSecretSha, - "callsecret" => DataTypes::CallSecret, + "sharedsecret" => DataTypes::SharedSecret, + "callid" => DataTypes::CallId, + "callname" => DataTypes::CallName, + "callsecretsha" => DataTypes::CallSecretSha, + "callsecret" => DataTypes::CallSecret, "sharedcallsecret" => DataTypes::SharedCallSecret, - "startdate" => DataTypes::StartDate, - "enddate" => DataTypes::EndDate, - "receiverid" => DataTypes::ReceiverId, - "senderid" => DataTypes::SenderId, - "signature" => DataTypes::Signature, - "signed" => DataTypes::Signed, - "message" => DataTypes::Message, - "lastping" => DataTypes::LastPing, - "pingiota" => DataTypes::PingIota, - "pingclients" => DataTypes::PingClients, - "matches" => DataTypes::Matches, - "omikron" => DataTypes::Omikron, - "loadedmessages" => DataTypes::LoadedMessages, - "messageamount" => DataTypes::MessageAmount, - "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::WantToWatch, - "watcher" => DataTypes::Watcher, - "createdat" => DataTypes::CreatedAt, - "username" => DataTypes::Username, - "display" => DataTypes::Display, - "avatar" => DataTypes::Avatar, - "about" => DataTypes::About, - "status" => DataTypes::Status, - "publickey" => DataTypes::PublicKey, - "sublevel" => DataTypes::SubLevel, - "subend" => DataTypes::SubEnd, + "startdate" => DataTypes::StartDate, + "enddate" => DataTypes::EndDate, + "receiverid" => DataTypes::ReceiverId, + "senderid" => DataTypes::SenderId, + "signature" => DataTypes::Signature, + "signed" => DataTypes::Signed, + "message" => DataTypes::Message, + "lastping" => DataTypes::LastPing, + "pingiota" => DataTypes::PingIota, + "pingclients" => DataTypes::PingClients, + "matches" => DataTypes::Matches, + "omikron" => DataTypes::Omikron, + "loadedmessages" => DataTypes::LoadedMessages, + "messageamount" => DataTypes::MessageAmount, + "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::WantToWatch, + "watcher" => DataTypes::Watcher, + "createdat" => DataTypes::CreatedAt, + "username" => DataTypes::Username, + "display" => DataTypes::Display, + "avatar" => DataTypes::Avatar, + "about" => DataTypes::About, + "status" => DataTypes::Status, + "publickey" => DataTypes::PublicKey, + "sublevel" => DataTypes::SubLevel, + "subend" => DataTypes::SubEnd, "communityaddress" => DataTypes::CommunityAddress, - "challenge" => DataTypes::Challenge, - "communitytitle" => DataTypes::CommunityTitle, - "communities" => DataTypes::Communities, + "challenge" => DataTypes::Challenge, + "communitytitle" => DataTypes::CommunityTitle, + "communities" => DataTypes::Communities, _ => DataTypes::ErrorType, // fallback if unknown } } @@ -153,7 +155,7 @@ impl DataTypes { #[derive(PartialEq, Debug)] pub enum CommunicationType { -Error, + Error, Success, Message, MessageLive, @@ -284,8 +286,8 @@ pub struct CommunicationValue { pub id: Uuid, pub comm_type: CommunicationType, pub log_value: Option, - pub sender: Option, - pub receiver: Option, + pub sender: Uuid, + pub receiver: Uuid, pub data: HashMap, } @@ -295,16 +297,16 @@ impl CommunicationValue { id: Uuid::new_v4(), comm_type, log_value: None, - sender: None, - receiver: None, + sender: Uuid::new_v4(), + receiver: Uuid::new_v4(), data: HashMap::new(), } } - pub fn with_id(mut self, p0: Uuid) -> Self{ + pub fn with_id(mut self, p0: Uuid) -> Self { self.id = p0; self } - pub fn get_id(&self) -> Uuid{ + pub fn get_id(&self) -> Uuid { self.id.clone() } pub fn with_log(mut self, log: LogValue) -> Self { @@ -315,19 +317,23 @@ impl CommunicationValue { &self.log_value } pub fn with_sender(mut self, sender: Uuid) -> Self { - self.sender = Some(sender); + self.sender = sender; self } - pub fn get_sender(&self) -> Option { + pub fn get_sender(&self) -> Uuid { self.sender.clone() } pub fn with_receiver(mut self, receiver: Uuid) -> Self { - self.receiver = Some(receiver); + self.receiver = receiver; self } - pub fn get_receiver(&self) -> Option { + pub fn get_receiver(&self) -> Uuid { self.receiver.clone() } + pub fn add_data_num(mut self, key: DataTypes, value: Number) -> Self { + self.data.insert(key, JsonValue::Number(value)); + self + } pub fn add_data_str(mut self, key: DataTypes, value: String) -> Self { self.data.insert(key, JsonValue::String(value)); self @@ -344,7 +350,7 @@ impl CommunicationValue { self.comm_type == p0 } pub fn to_json(&self) -> JsonValue { - let mut jdata = object!{}; + let mut jdata = object! {}; for (k, v) in &self.data { jdata[&format!("{:?}", k)] = JsonValue::from(v.clone()); } @@ -352,20 +358,26 @@ impl CommunicationValue { object! { id: self.id.to_string(), type: format!("{:?}", self.comm_type), - sender: self.sender.map(|u| u.to_string()).unwrap_or_default(), - receiver: self.receiver.map(|u| u.to_string()).unwrap_or_default(), + sender: self.sender.to_string(), + receiver: self.receiver.to_string(), log: self.log_value.as_ref().map(|l| l.clone().to_json()).unwrap_or(JsonValue::Null), data: jdata } } - + pub fn from_json(json_str: &str) -> Self { let parsed = parse(json_str).unwrap(); let comm_type = CommunicationType::parse(parsed["type"].to_string()); - let sender = parsed["sender"].as_str().and_then(|s| Uuid::parse_str(s).ok()); - let receiver = parsed["receiver"].as_str().and_then(|s| Uuid::parse_str(s).ok()); + let sender: Uuid = parsed["sender"] + .as_str() + .and_then(|s| Uuid::parse_str(s).ok()) + .unwrap_or(Uuid::new_v4()); + let receiver: Uuid = parsed["receiver"] + .as_str() + .and_then(|s| Uuid::parse_str(s).ok()) + .unwrap_or(Uuid::new_v4()); let log_value = if parsed["log"].is_object() { Some(LogValue::new( @@ -393,34 +405,45 @@ impl CommunicationValue { data, } } - pub fn ack_message(message_id: Uuid, sender: Option) -> CommunicationValue { - let mut cv = CommunicationValue::new(CommunicationType::Message) - .with_id(message_id); + pub fn ack_message(message_id: Uuid, sender: Uuid) -> CommunicationValue { + let mut cv = CommunicationValue::new(CommunicationType::Message).with_id(message_id); - if let Some(s) = sender { + if let s = sender { cv = cv.add_data(DataTypes::SenderId, JsonValue::String(s.to_string())); } cv } pub fn forward_to_other_iota(original: &mut CommunicationValue) -> CommunicationValue { - let receiver = Uuid::from_str(&*original.get_data(DataTypes::ReceiverId).unwrap().to_string()).ok() - .or(Option::from(Uuid::nil())); + let receiver = Uuid::from_str( + &*original + .get_data(DataTypes::ReceiverId) + .unwrap() + .to_string(), + ) + .ok() + .or(Option::from(Uuid::nil())); let now_ms = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_millis() as i64; - let mut cv = CommunicationValue::new(CommunicationType::MessageOtherIota) + let cv = CommunicationValue::new(CommunicationType::MessageOtherIota) .with_id(original.get_id()) .with_receiver(receiver.unwrap()) .add_data(DataTypes::SendTime, JsonValue::String(now_ms.to_string())) - .add_data(DataTypes::MessageContent, JsonValue::String(original.get_data(DataTypes::MessageContent).unwrap().to_string())); + .add_data( + DataTypes::MessageContent, + JsonValue::String( + original + .get_data(DataTypes::MessageContent) + .unwrap() + .to_string(), + ), + ); // include sender_id if the original had one - if let Some(sender) = original.get_sender() { - cv = cv.add_data(DataTypes::SenderId, JsonValue::String(sender.to_string())); - } - cv + let sender = original.get_sender(); + cv.add_data(DataTypes::SenderId, JsonValue::String(sender.to_string())) } -} \ No newline at end of file +} diff --git a/src/gui/log_panel.rs b/src/gui/log_panel.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/gui/settings_panel.rs b/src/gui/settings_panel.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/gui/user_panel.rs b/src/gui/user_panel.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/langu/language_creator.rs b/src/langu/language_creator.rs new file mode 100644 index 0000000..619542a --- /dev/null +++ b/src/langu/language_creator.rs @@ -0,0 +1,58 @@ +use crate::util::file_util::{self, save_file}; +use json::{self, JsonValue}; + +pub fn create_languages() { + let mut frontend_messages = JsonValue::new_object(); + let mut omikron_messages = JsonValue::new_object(); + let mut button_texts = JsonValue::new_object(); + let mut general_texts = JsonValue::new_object(); + let mut debug_messages = JsonValue::new_object(); + + // FRONTEND + frontend_messages.insert( + "USER_CONTEXT_GET_CONVERSATIONS", + "User {} is loading conversations", + ); + frontend_messages.insert( + "USER_CONTEXT_GET_COMMUNITIES", + "User {} is loading communities", + ); + frontend_messages.insert("ADD_CONVERSATION", "User {} added {}"); + + // OMIKRON + omikron_messages.insert( + "IdentificationResponse", + "IOTA identified on Omikron, {} users!", + ); + + // BUTTONS + button_texts.insert("EXIT", "Exit"); + + // GENERAL + general_texts.insert("IOTA_ID", "IOTA ID: {}-####-####-####-############"); + general_texts.insert("USER_ID", "USER ID: {}"); + general_texts.insert("USER_IDS", "USER IDS: {}"); + + // DEBUG + debug_messages.insert("", ""); + save_file( + "languages/en_INT", + "frontend.json", + &frontend_messages.to_string(), + ); + save_file( + "languages/en_INT", + "omikron.json", + &omikron_messages.to_string(), + ); + save_file( + "languages/en_INT", + "buttons.json", + &button_texts.to_string(), + ); + save_file( + "languages/en_INT", + "debug.json", + &debug_messages.to_string(), + ); +} diff --git a/src/langu/language_manager.rs b/src/langu/language_manager.rs new file mode 100644 index 0000000..41076fa --- /dev/null +++ b/src/langu/language_manager.rs @@ -0,0 +1,66 @@ +use crate::util::file_util::{self, load_file}; +use json::{JsonValue, parse}; +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Mutex; + +#[derive(Clone)] +pub struct LanguagePack { + languages: HashMap, +} + +// Language packs need to have formatting +// Variables need to be provided + +pub static LANGUAGE_PACK: Lazy> = + Lazy::new(|| Mutex::new(LanguagePack::new("en_INT"))); + +pub fn get_language() -> LanguagePack { + LANGUAGE_PACK.lock().unwrap().clone() +} + +impl LanguagePack { + pub fn new(language: &str) -> Self { + let mut pack = LanguagePack { + languages: HashMap::new(), + }; + pack.load_language(language); + pack + } + + pub fn load_language(&mut self, language: &str) { + let path = format!("languages/{}/", language); + + let frontend_messages = file_util::load_file(&path, "frontend.json"); + let frontend_messages = parse(&frontend_messages).unwrap(); + for (key, value) in frontend_messages.entries() { + self.languages + .insert(key.to_string(), value.as_str().unwrap().to_string()); + } + + let omikron_messages = file_util::load_file(&path, "omikron.json"); + let omikron_messages = parse(&omikron_messages).unwrap(); + for (key, value) in omikron_messages.entries() { + self.languages + .insert(key.to_string(), value.as_str().unwrap().to_string()); + } + + let button_texts = file_util::load_file(&path, "buttons.json"); + let button_texts = parse(&button_texts).unwrap(); + for (key, value) in button_texts.entries() { + self.languages + .insert(key.to_string(), value.as_str().unwrap().to_string()); + } + + let debug_messages = file_util::load_file(&path, "debug.json"); + let debug_messages = parse(&debug_messages).unwrap(); + for (key, value) in debug_messages.entries() { + self.languages + .insert(key.to_string(), value.as_str().unwrap().to_string()); + } + } + + pub fn get_translation(&self, key: &str) -> &String { + self.languages.get(key).unwrap() + } +} diff --git a/src/langu/mod.rs b/src/langu/mod.rs new file mode 100644 index 0000000..352d639 --- /dev/null +++ b/src/langu/mod.rs @@ -0,0 +1,2 @@ +pub mod language_creator; +pub mod language_manager; diff --git a/src/main.rs b/src/main.rs index bc2b0d7..2f363d7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,59 +1,99 @@ +use base64::Engine; +use base64::engine::general_purpose; +use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; use futures_util::SinkExt; -use json::{self, JsonValue}; use json::JsonValue::String; +use json::{self, JsonValue}; +use rand::Rng; +use rand_core::OsRng; +use rand_core::RngCore; +use reqwest::header::PUBLIC_KEY_PINS_REPORT_ONLY; +use sha2::{Digest, Sha256}; +use std::sync::Arc; use uuid::Uuid; +use x448::{PublicKey, Secret}; mod data; -mod omikron; -mod util; -mod users; mod eula; +mod omikron; +mod users; +mod util; mod gui { pub mod ratatui_interface; } mod auth; +mod langu; +use crate::auth::auth_connector::AuthConnector; +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; use crate::eula::*; -use crate::omikron::omikron_connection::{OmikronConnection}; -use crate::data::communication::{CommunicationValue, CommunicationType, DataTypes}; +use crate::langu::language_creator; +use crate::langu::language_manager; +use crate::omikron::omikron_connection::OmikronConnection; +use crate::omikron::ping_pong_task::PingPongTask; use crate::users::user_manager::UserManager; -use crate::util::config_util::ConfigUtil; +use crate::users::user_profile::UserProfile; +use crate::users::user_profile_full::UserProfileFull; +use crate::util::config_util::{self, CONFIG, ConfigUtil}; +use crate::util::file_util; #[tokio::main] async fn main() { - if(!eula_checker::check_eula()){ - println!("Please accept the end user license agreement before launching!"); - return; - } - - let mut c_util = ConfigUtil::new(); - c_util.load(); - if !c_util.config.has_key("iota_id") { - c_util.change("iota_id", Uuid::new_v4()); - c_util.save(); + // EULA + //if !eula_checker::check_eula() { + // println!("Please accept the end user license agreement before launching!"); + // return; + //} + + // LANGUAGE PACK + language_creator::create_languages(); + + // BASIC CONFIGURATION + CONFIG.lock().unwrap().load(); + if !CONFIG.lock().unwrap().config.has_key("iota_id") { + CONFIG.lock().unwrap().change("iota_id", Uuid::new_v4()); + CONFIG.lock().unwrap().save(); } - + // USER MANAGEMENT UserManager::load_users().await; + let mut sb = "".to_string(); for up in UserManager::get_users() { - sb = sb + "," + &*up.user_id.to_string(); + sb = sb + "," + &up.user_id.to_string().as_str(); } + sb.remove(0); + println!( + "IOTA ID: {}-####-####-####-############", + CONFIG + .lock() + .unwrap() + .get_iota_id() + .to_string() + .split("-") + .next() + .unwrap() + ); + println!("User ID: {}", sb); - UserManager::save_users(); + // IDENTIFICATION ON OMIKRON let omikron: OmikronConnection = OmikronConnection::new(); omikron.connect().await; - omikron.send_message( - CommunicationValue::new( - CommunicationType::Identification + let _ping_pong_task = PingPongTask::new(Arc::new(OmikronConnection::new())); + omikron + .send_message( + CommunicationValue::new(CommunicationType::Identification) + .add_data(DataTypes::UserIds, String(sb.to_string())) + .add_data( + DataTypes::IotaId, + String(CONFIG.lock().unwrap().get_iota_id().to_string()), + ) + .to_json() + .to_string() + .as_mut() + .to_string(), ) - .add_data(DataTypes::UserIds, String(sb.to_string())) - .add_data(DataTypes::IotaId, String(c_util.config["iota_id"].to_string())) - .to_json() - .to_string() - .as_mut() - .to_string() - ).await; - + .await; + loop {} } diff --git a/src/omikron/mod.rs b/src/omikron/mod.rs index 4249352..acb0f59 100644 --- a/src/omikron/mod.rs +++ b/src/omikron/mod.rs @@ -1 +1,2 @@ -pub mod omikron_connection; \ No newline at end of file +pub mod omikron_connection; +pub mod ping_pong_task; diff --git a/src/omikron/omikron_connection.rs b/src/omikron/omikron_connection.rs index bb0b575..12c7ffb 100644 --- a/src/omikron/omikron_connection.rs +++ b/src/omikron/omikron_connection.rs @@ -1,10 +1,12 @@ use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::omikron::ping_pong_task::PingPongTask; use crate::users::contact::Contact; use crate::users::user_community_util::UserCommunityUtil; use crate::util::chat_files::ChatFiles; use crate::util::chats_util::{get_user, get_users, mod_user}; use futures_util::{SinkExt, StreamExt}; use json::JsonValue; +use json::number::Number; use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; @@ -50,6 +52,7 @@ impl OmikronConnection { let (write_half, read_half) = ws_stream.split(); *self.writer.lock().await = Some(write_half); self.spawn_listener(read_half); + self.start_ping_pong_task().await; break; } Err(e) => { @@ -59,8 +62,22 @@ impl OmikronConnection { } } - /// Gracefully close connection - pub async fn close(&self) { + // Start the PingPongTask + pub async fn start_ping_pong_task(&self) { + let ping_pong_task = PingPongTask::new(Arc::new(self.clone())); + + // Store the task handle in `pingpong` so we can manage it + let mut pingpong_handle = self.pingpong.lock().await; + *pingpong_handle = Some(tokio::spawn(async move { + ping_pong_task.run_ping_loop(); + })); + } + + pub async fn send_message(&self, msg: String) { + Self::send_message_static(&self.writer, msg).await; + } + + pub async fn disconnect(&self) { if let Some(handle) = self.pingpong.lock().await.take() { handle.abort(); } @@ -88,14 +105,14 @@ impl OmikronConnection { } Ok(Message::Text(text)) => { let mut cv = CommunicationValue::from_json(&text); // needs CommunicationValue parser - println!("[Omikron] Received message: {:?}", cv); if cv.is_type(CommunicationType::Pong) { - println!("[Omikron] Pong received"); // handle pingpong reset here + continue; } // ************************************************ // // Direct messages // // ************************************************ // + println!("[Omikron] Received message: {:?}", cv); if cv.is_type(CommunicationType::MessageOtherIota) { let sender_id = &cv.get_sender(); let receiver_id = &cv.get_receiver(); @@ -105,8 +122,8 @@ impl OmikronConnection { .as_i64() .unwrap_or(-1), false, - receiver_id.unwrap(), - sender_id.unwrap(), + *receiver_id, + *sender_id, cv.get_data(DataTypes::MessageContent) .unwrap() .as_str() @@ -114,7 +131,7 @@ impl OmikronConnection { ); let response = CommunicationValue::new(CommunicationType::MessageLive) .with_id(cv.get_id()) - .with_receiver(cv.get_receiver().unwrap()) + .with_receiver(cv.get_receiver()) .add_data( DataTypes::SendTime, cv.get_data(DataTypes::SendTime).unwrap().clone(), @@ -125,7 +142,7 @@ impl OmikronConnection { ) .add_data( DataTypes::SenderId, - JsonValue::String(cv.get_sender().unwrap().clone().to_string()), + JsonValue::String(cv.get_sender().clone().to_string()), ); Self::send_message_static( &writer.clone(), @@ -143,7 +160,7 @@ impl OmikronConnection { .unwrap() .as_millis() as i64, true, - my_id.unwrap(), + my_id, Uuid::from_str( &*cv.get_data(DataTypes::ReceiverId).unwrap().to_string(), ) @@ -165,23 +182,23 @@ impl OmikronConnection { } if cv.is_type(CommunicationType::MessageGet) { - let my_id = cv.get_sender().unwrap(); + let my_id = cv.get_sender(); let partner_id = Uuid::from_str( &*cv.get_data(DataTypes::ChatPartnerId).unwrap().to_string(), ) .unwrap(); let offset = cv .get_data(DataTypes::LoadedMessages) - .unwrap() + .unwrap_or(&JsonValue::Null) .to_string() .parse::() - .unwrap(); + .unwrap_or(0); let amount = cv .get_data(DataTypes::MessageAmount) - .unwrap() + .unwrap_or(&JsonValue::Null) .to_string() .parse::() - .unwrap(); + .unwrap_or(0); let messages = ChatFiles::get_messages(my_id, partner_id, offset, amount); // needs ChatFiles @@ -198,11 +215,12 @@ impl OmikronConnection { if cv.is_type(CommunicationType::GetChats) { let user_id = cv.get_sender(); - let users = get_users(user_id.unwrap()); // needs ChatsUtil + let users = get_users(user_id); // needs ChatsUtil let resp = CommunicationValue::new(CommunicationType::GetChats) .with_id(cv.get_id()) - .with_receiver(user_id.unwrap()) + .with_receiver(user_id) .add_data(DataTypes::UserIds, users); + println!("ALARM: {}", resp.to_json().to_string()); Self::send_message_static(&writer.clone(), resp.to_json().to_string()) .await; continue; @@ -214,18 +232,18 @@ impl OmikronConnection { &*cv.get_data(DataTypes::UserId).unwrap().to_string(), ) .unwrap(); - let mut contact = get_user(user_id.unwrap(), other_id) - .unwrap_or(Contact::new(other_id)); // needs ChatsUtil + Contact + let mut contact = + get_user(user_id, other_id).unwrap_or(Contact::new(other_id)); // needs ChatsUtil + Contact contact.set_last_message_at( SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_millis() as i64, ); - mod_user(user_id.unwrap(), &contact); + mod_user(user_id, &contact); let resp = CommunicationValue::new(CommunicationType::AddChat) .with_id(cv.get_id()) - .with_receiver(user_id.unwrap()); + .with_receiver(user_id); Self::send_message_static(&writer.clone(), resp.to_json().to_string()) .await; continue; @@ -233,7 +251,7 @@ impl OmikronConnection { if cv.is_type(CommunicationType::AddCommunity) { UserCommunityUtil::add_community( - cv.get_sender().unwrap(), + cv.get_sender(), cv.get_data(DataTypes::CommunityAddress) .unwrap() .to_string(), @@ -242,7 +260,7 @@ impl OmikronConnection { ); let resp = CommunicationValue::new(CommunicationType::AddCommunity) .with_id(cv.get_id()) - .with_receiver(cv.get_sender().unwrap()); + .with_receiver(cv.get_sender()); Self::send_message_static(&writer.clone(), resp.to_json().to_string()) .await; continue; @@ -251,10 +269,10 @@ impl OmikronConnection { if cv.is_type(CommunicationType::GetCommunities) { let resp = CommunicationValue::new(CommunicationType::GetCommunities) .with_id(cv.get_id()) - .with_receiver(cv.get_sender().unwrap()) + .with_receiver(cv.get_sender()) .add_data( DataTypes::Communities, - UserCommunityUtil::get_communities(cv.get_sender().unwrap()), + UserCommunityUtil::get_communities(cv.get_sender()), ); // needs UserCommunityUtil Self::send_message_static(&writer.clone(), resp.to_json().to_string()) .await; @@ -263,14 +281,14 @@ impl OmikronConnection { if cv.is_type(CommunicationType::RemoveCommunity) { UserCommunityUtil::remove_community( - cv.get_sender().unwrap(), + cv.get_sender(), cv.get_data(DataTypes::CommunityAddress) .unwrap() .to_string(), ); // needs UserCommunityUtil let resp = CommunicationValue::new(CommunicationType::RemoveCommunity) .with_id(cv.get_id()) - .with_receiver(cv.get_sender().unwrap()); + .with_receiver(cv.get_sender()); Self::send_message_static(&writer.clone(), resp.to_json().to_string()) .await; continue; @@ -285,9 +303,6 @@ impl OmikronConnection { } }); } - pub async fn send_message(&self, msg: String) { - Self::send_message_static(&self.writer, msg).await; - } pub async fn send_message_static( writer: &Arc< Mutex< @@ -322,10 +337,18 @@ impl OmikronConnection { } }); } -} -#[tokio::main] -async fn main() { - let conn = OmikronConnection::new(); - conn.connect().await; + pub async fn send_ping_message(&self, uuid: Uuid) { + // Send the ping message over the connection + let ping_message = CommunicationValue::new(CommunicationType::Ping) + .with_id(uuid) + .add_data_num(DataTypes::LastPing, Number::from(2)) + .to_json() + .to_string(); + self.send_message(ping_message).await; + } + pub async fn reconnect(&self) { + self.disconnect().await; + self.connect().await; + } } diff --git a/src/omikron/ping_pong_task.rs b/src/omikron/ping_pong_task.rs new file mode 100644 index 0000000..21fa900 --- /dev/null +++ b/src/omikron/ping_pong_task.rs @@ -0,0 +1,116 @@ +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; +use crate::omikron::omikron_connection::OmikronConnection; +use color_eyre::owo_colors::OwoColorize; +use futures_util::{SinkExt, StreamExt}; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::Mutex; +use tokio::time::{Duration, Instant, sleep}; +use uuid::Uuid; + +#[derive(Clone)] +pub struct PingPongTask { + pub parent: Arc, // assuming OmikronConnection is your connection type + pub message_send_times: Arc>>, + pub no_ping_in: Arc>, + pub last_ping: Arc>>, +} + +impl PingPongTask { + pub fn new(parent: Arc) -> Self { + let message_send_times = Arc::new(Mutex::new(HashMap::new())); + let no_ping_in = Arc::new(Mutex::new(-1)); + let last_ping = Arc::new(Mutex::new(None)); + + let task = PingPongTask { + parent: parent.clone(), + message_send_times: message_send_times.clone(), + no_ping_in: no_ping_in.clone(), + last_ping: last_ping.clone(), + }; + + // Spawn the periodic ping task + tokio::spawn({ + let task = task.clone(); // Clone the task (Arc) so that it lives long enough for the async task + async move { + task.run_ping_loop().await; + } + }); + + task + } + + pub async fn run_ping_loop(&self) { + loop { + sleep(Duration::from_secs(5)).await; + self.send_ping().await; + } + } + + pub async fn send_ping(&self) { + let uuid = Uuid::new_v4(); + let send_time = Instant::now(); + + { + let mut message_send_times = self.message_send_times.lock().await; + message_send_times.insert(uuid, send_time); + } + + let no_ping_in = { + let no_ping_in = self.no_ping_in.lock().await; + *no_ping_in + }; + + if no_ping_in != -1 { + // Connection slow or disconnected + self.handle_slow_connection(no_ping_in).await; + } else { + // Connection is fine + self.parent.send_ping_message(uuid).await; + } + } + + pub async fn handle_slow_connection(&self, no_ping_in: i32) { + if no_ping_in > 8 { + // Attempt reconnection if ping times out + self.parent.reconnect().await; + self.reconnect().await; + } + } + + pub async fn reconnect(&self) { + let mut no_ping_in = self.no_ping_in.lock().await; + *no_ping_in = -1; // Reset slow count + } + + pub async fn handle_pong(&self, cv: &CommunicationValue) { + let send_time = { + let message_send_times = self.message_send_times.lock().await; + message_send_times.get(&cv.get_id()).cloned() + }; + + if let Some(send_time) = send_time { + let receive_time = Instant::now(); + let ping = receive_time.duration_since(send_time).as_millis() as u64; + + { + let mut last_ping = self.last_ping.lock().await; + *last_ping = Some(ping); + } + + { + let mut no_ping_in = self.no_ping_in.lock().await; + *no_ping_in = -1; + } + } + } + + pub async fn cancel(&self) { + // Cancel or stop the task + let mut no_ping_in = self.no_ping_in.lock().await; + *no_ping_in = -1; // Reset the counter + + // Example of stopping the ping task gracefully + // If using a task join handle or similar + } +} diff --git a/src/users/contact.rs b/src/users/contact.rs index dced7f8..bc241cb 100644 --- a/src/users/contact.rs +++ b/src/users/contact.rs @@ -14,11 +14,11 @@ pub struct Contact { #[derive(Debug, Clone)] pub enum UserStatus { Online, - DoNotDisturb, + Do_Not_Disturb, WC, Away, - UserOffline, - IotaOffline, + User_Offline, + Iota_Offline, } impl Default for Contact { @@ -31,7 +31,7 @@ impl Default for Contact { user_id: None, user_name: None, last_message_at: Some(now), - user_status: UserStatus::UserOffline, + user_status: UserStatus::User_Offline, about: None, } } @@ -43,7 +43,7 @@ impl Contact { user_id: Some(user_id), user_name: None, last_message_at: Some(last_message_at), - user_status: UserStatus::UserOffline, + user_status: UserStatus::User_Offline, about: None, } } @@ -53,7 +53,7 @@ impl Contact { user_id: Some(user_id), user_name: None, last_message_at: None, - user_status: UserStatus::UserOffline, + user_status: UserStatus::User_Offline, about: None, } } @@ -64,13 +64,13 @@ impl Contact { pub fn to_json(&self) -> JsonValue { let mut obj = JsonValue::new_object(); if let Some(id) = &self.user_id { - obj["userID"] = JsonValue::from(id.to_string()); + obj["user_id"] = JsonValue::from(id.to_string()); } if let Some(name) = &self.user_name { - obj["userName"] = JsonValue::from(name.as_str()); + obj["user_name"] = JsonValue::from(name.as_str()); } if let Some(ts) = &self.last_message_at { - obj["lastMessageAt"] = JsonValue::from(ts.to_string()); + obj["last_message_at"] = JsonValue::from(ts.to_string()); } obj } @@ -81,29 +81,27 @@ impl Contact { } pub fn from_json(o: &JsonValue) -> Contact { - let user_id = o["userID"] - .as_str() - .and_then(|s| Uuid::parse_str(s).ok()); + let user_id = o["user_id"].as_str().and_then(|s| Uuid::parse_str(s).ok()); - let user_name = o["userName"].as_str().map(|s| s.to_string()); + let user_name = o["user_name"].as_str().map(|s| s.to_string()); - let last_message_at = o["lastMessageAt"].as_i64(); + let last_message_at = o["last_message_at"].as_i64(); Contact { user_id, user_name, last_message_at, - user_status: UserStatus::UserOffline, // default + user_status: UserStatus::User_Offline, // default about: None, } } pub fn info(&self) -> JsonValue { let mut obj = self.to_json(); if let Some(id) = &self.user_id { - obj["userID"] = JsonValue::from(id.to_string()); + obj["user_id"] = JsonValue::from(id.to_string()); } if let Some(name) = &self.user_name { - obj["userName"] = JsonValue::from(name.as_str()); + obj["user_name"] = JsonValue::from(name.as_str()); } obj } diff --git a/src/users/mod.rs b/src/users/mod.rs index ff0baeb..1623ca2 100644 --- a/src/users/mod.rs +++ b/src/users/mod.rs @@ -1,5 +1,5 @@ pub mod contact; +pub mod user_community_util; pub mod user_manager; -pub mod user_profile_full; pub mod user_profile; -pub mod user_community_util; \ No newline at end of file +pub mod user_profile_full; diff --git a/src/users/user_community_util.rs b/src/users/user_community_util.rs index f7adeb6..d0764ed 100644 --- a/src/users/user_community_util.rs +++ b/src/users/user_community_util.rs @@ -6,12 +6,7 @@ use uuid::Uuid; pub struct UserCommunityUtil; impl UserCommunityUtil { - pub fn add_community( - storage_owner: Uuid, - address: String, - title: String, - position: String, - ) { + pub fn add_community(storage_owner: Uuid, address: String, title: String, position: String) { let path = format!("users/{}/communities.json", storage_owner); let mut communities: JsonValue = Self::load_array(&path); @@ -58,7 +53,7 @@ impl UserCommunityUtil { if let Some(parent) = Path::new(path).parent() { let _ = fs::create_dir_all(parent); } - + let _ = fs::write(path, arr.pretty(3)); } } diff --git a/src/users/user_manager.rs b/src/users/user_manager.rs index e8b7ae1..4d74b91 100644 --- a/src/users/user_manager.rs +++ b/src/users/user_manager.rs @@ -1,16 +1,26 @@ -use std::fs; -use std::sync::Mutex; -use std::io; -use std::path::Path; -use uuid::Uuid; -use rand::Rng; -use rand::rngs::OsRng; -use base64::{engine::general_purpose, Engine as _}; -use json::{JsonValue}; -use once_cell::sync::Lazy; +use crate::auth::auth_connector::AuthConnector; use crate::users::user_profile::UserProfile; use crate::users::user_profile_full::UserProfileFull; +use crate::util::config_util::CONFIG; use crate::util::file_util::{load_file, save_file}; +use base64::{Engine as _, engine::general_purpose}; +use der::DerOrd; +use hex; +use json::JsonValue; +use once_cell::sync::Lazy; +use pkcs8::EncodePublicKey; +use pkcs8::spki::SubjectPublicKeyInfoOwned; +use pkcs8::{ObjectIdentifier, PrivateKeyInfo, SubjectPublicKeyInfo, der::Encode}; +use rand::Rng; +use rand_core::OsRng; +use rand_core::RngCore; +use rustls::pki_types::SubjectPublicKeyInfoDer; +use sha2::{Digest, Sha256}; +use std::io; +use std::sync::Mutex; +use uuid::Uuid; +use x448::{PublicKey, Secret}; +use x509::AlgorithmIdentifier; pub struct UserManager; @@ -18,14 +28,22 @@ static USERS: Lazy>> = Lazy::new(|| Mutex::new(Vec::new() static UNIQUE: Lazy> = Lazy::new(|| Mutex::new(false)); impl UserManager { - pub fn create_user(username: &str) -> Option { - // Stub: normally AuthConnector.getRegister() returns a UUID - let user_id = Uuid::new_v4(); + pub async fn create_user(username: &str) -> Option { + let user_id = AuthConnector::get_register().await.unwrap(); + let mut buf = [0u8; 56]; + let mut rng = OsRng; + rng.fill_bytes(&mut buf); + let private_key = Secret::from_bytes(&buf).unwrap(); + let public_key = PublicKey::from(&private_key); - // Stubbed: CryptoHelper.generateKeyPair() - let public_key = general_purpose::STANDARD.encode(b"dummy-public"); - let private_key = general_purpose::STANDARD.encode(b"dummy-private"); - let private_key_hash = format!("hash-{}", &private_key); + let mut hasher = Sha256::new(); + hasher.update( + &general_purpose::STANDARD + .encode(&private_key.as_bytes()) + .as_bytes(), + ); + let result = hasher.finalize(); + let private_key_hash = hex::encode(result); let mut bytes = [0u8; 192]; OsRng.fill(bytes.as_mut()); @@ -35,12 +53,27 @@ impl UserManager { user_id, username.to_string(), None, - public_key, + general_purpose::STANDARD.encode(&public_key.as_bytes()), private_key_hash, reset_token, ); - let up_full = UserProfileFull { user_profile: up.clone(), private_key }; + let up_full = UserProfileFull { + user_profile: up.clone(), + private_key: general_purpose::STANDARD.encode(&private_key.as_bytes()), + }; + + AuthConnector::complete_register(&up, &CONFIG.lock().unwrap().get_iota_id().to_string()) + .await; + save_file( + "", + &format!("{}.tu", username), + &format!( + "{}::{}", + user_id, + general_purpose::STANDARD.encode(&private_key.as_bytes()) + ), + ); USERS.lock().unwrap().push(up); Self::save_users().ok(); @@ -48,7 +81,12 @@ impl UserManager { } pub fn get_user(user_id: Uuid) -> Option { - USERS.lock().unwrap().iter().cloned().find(|u| u.user_id == user_id) + USERS + .lock() + .unwrap() + .iter() + .cloned() + .find(|u| u.user_id == user_id) } pub fn get_users() -> Vec { diff --git a/src/users/user_profile.rs b/src/users/user_profile.rs index 38364fd..88b8b06 100644 --- a/src/users/user_profile.rs +++ b/src/users/user_profile.rs @@ -1,15 +1,15 @@ -use std::fs; -use std::sync::Mutex; -use std::collections::HashMap; -use std::io; -use std::path::Path; -use uuid::Uuid; +use crate::auth::auth_connector::AuthConnector; +use crate::users::user_manager::UserManager; +use base64::{Engine as _, engine::general_purpose}; +use json::{JsonValue, object, stringify}; use rand::Rng; use rand::rngs::OsRng; -use base64::{engine::general_purpose, Engine as _}; -use json::{JsonValue, object, stringify}; -use crate::users::user_manager::UserManager; -use crate::auth::auth_connector::AuthConnector; +use std::collections::HashMap; +use std::fs; +use std::io; +use std::path::Path; +use std::sync::Mutex; +use uuid::Uuid; // --- UserProfile --- #[derive(Clone, Debug)] @@ -43,34 +43,42 @@ impl UserProfile { pub fn to_json(&self) -> JsonValue { let mut obj = object! { - "UUID" => self.user_id.to_string(), + "uuid" => self.user_id.to_string(), "username" => self.username.clone(), - "publicKey" => self.public_key.clone(), - "privateKeyHash" => self.private_key_hash.clone(), - "resetToken" => self.reset_token.clone() + "public_key" => self.public_key.clone(), + "private_key_hash" => self.private_key_hash.clone(), + "reset_token" => self.reset_token.clone() }; if let Some(d) = &self.display_name { - obj["displayName"] = d.clone().into(); + obj["display_name"] = d.clone().into(); } obj } pub async fn from_json(j: &JsonValue) -> Option { - let uuid = Uuid::parse_str(j["UUID"].as_str()?).ok()?; + let uuid = Uuid::parse_str(j["uuid"].as_str()?).ok()?; let username = j["username"].as_str()?.to_string(); - let public_key = j["publicKey"].as_str()?.to_string(); - let private_key_hash = j["privateKeyHash"].as_str()?.to_string(); - let reset_token = j["resetToken"].as_str()?.to_string(); - let display_name = j["displayName"].as_str().map(|s| s.to_string()); + let public_key = j["public_key"].as_str()?.to_string(); + let private_key_hash = j["private_key_hash"].as_str()?.to_string(); + let reset_token = j["reset_token"].as_str()?.to_string(); + let display_name = j["display_name"].as_str().map(|s| s.to_string()); - let mut up = UserProfile::new(uuid, username, display_name, public_key, private_key_hash, reset_token); + let mut up = UserProfile::new( + uuid, + username, + display_name, + public_key, + private_key_hash, + reset_token, + ); // Migration hook (stubbed, since AuthConnector isn’t implemented here) - if j.has_key("migrate") - || j.has_key("migrating") - || j.has_key("changing") - || j.has_key("move") - || j.has_key("moving") { + if j.has_key("migrate") + || j.has_key("migrating") + || j.has_key("changing") + || j.has_key("move") + || j.has_key("moving") + { if AuthConnector::migrate_user(&mut up, stringify!("{}", Uuid::new_v4())).await { println!("[INFO] Migration triggered for {}", up.username); UserManager::set_unique(true); @@ -90,6 +98,8 @@ impl UserProfile { } pub fn get_display_name(&self) -> String { - self.display_name.clone().unwrap_or_else(|| self.username.clone()) + self.display_name + .clone() + .unwrap_or_else(|| self.username.clone()) } -} \ No newline at end of file +} diff --git a/src/users/user_profile_full.rs b/src/users/user_profile_full.rs index ad0b6b1..f08f7d7 100644 --- a/src/users/user_profile_full.rs +++ b/src/users/user_profile_full.rs @@ -5,4 +5,4 @@ use crate::users::user_profile::UserProfile; pub struct UserProfileFull { pub user_profile: UserProfile, pub private_key: String, -} \ No newline at end of file +} diff --git a/src/util/chats_util.rs b/src/util/chats_util.rs index eda887b..618b7a2 100644 --- a/src/util/chats_util.rs +++ b/src/util/chats_util.rs @@ -18,8 +18,8 @@ pub fn mod_user(storage_owner: Uuid, contact: &Contact) { }; for i in 0..contacts.len() { - if contacts[i]["userID"].as_str() == Some(&contact.user_id.unwrap().to_string()) { - contacts.remove(stringify!("{}", i)); + if contacts[i]["user_id"].as_str() == Some(&contact.user_id.unwrap().to_string()) { + contacts.array_remove(i); break; } } @@ -37,7 +37,7 @@ pub fn get_user(storage_owner: Uuid, user_id: Uuid) -> Option { if let Ok(contacts) = json::parse(&s) { for i in 0..contacts.len() { - if let Some(uid) = contacts[i]["userID"].as_str() { + if let Some(uid) = contacts[i]["user_id"].as_str() { if Uuid::parse_str(uid).ok()? == user_id { return Option::from(Contact::from_json(&contacts[i])); } diff --git a/src/util/config_util.rs b/src/util/config_util.rs index 54867ea..08bbde6 100644 --- a/src/util/config_util.rs +++ b/src/util/config_util.rs @@ -1,9 +1,13 @@ use crate::util::file_util::{load_file, save_file}; use json::JsonValue; +use once_cell::sync::Lazy; use std::fs::{self, File}; use std::path::Path; +use std::sync::Mutex; use uuid::Uuid; +pub static CONFIG: Lazy> = Lazy::new(|| Mutex::new(ConfigUtil::new())); + pub struct ConfigUtil { pub config: JsonValue, pub unique: bool, @@ -24,6 +28,14 @@ impl ConfigUtil { } } + pub fn get_iota_id(&self) -> Uuid { + self.config["iota_id"] + .as_str() + .unwrap_or_default() + .parse() + .unwrap_or_default() + } + pub fn change(&mut self, key: &str, value: Uuid) { self.config[key] = JsonValue::String(value.to_string()); self.unique = true; diff --git a/src/util/mod.rs b/src/util/mod.rs index 6f1977e..b326b92 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,4 @@ -pub mod file_util; pub mod chat_files; +pub mod chats_util; pub mod config_util; -pub mod chats_util; \ No newline at end of file +pub mod file_util;