From 5b5fbce22fbd6181488356202b58c41c514a601a Mon Sep 17 00:00:00 2001 From: Alex-Emmet Date: Sun, 18 Jan 2026 21:42:57 +0100 Subject: [PATCH] Client auth, Encryption module --- .gitignore | 2 +- src/communities/interactables/category.rs | 246 ++-- src/communities/interactables/interactable.rs | 68 +- src/communities/interactables/registry.rs | 150 +-- src/communities/interactables/text_chat.rs | 524 ++++----- src/communities/interactables/voice_chat.rs | 380 +++---- src/communities/mod.rs | 26 +- src/communities/perms/permission.rs | 54 +- src/data/communication.rs | 17 +- src/eula/eula_checker.rs | 52 +- src/gui/log_panel.rs | 216 ++-- src/omikron/omikron_connection.rs | 6 +- src/users/mod.rs | 8 +- src/util/crypto_helper.rs | 258 ++--- src/util/crypto_util.rs | 1004 +++++++++++++++++ 15 files changed, 2008 insertions(+), 1003 deletions(-) create mode 100644 src/util/crypto_util.rs diff --git a/.gitignore b/.gitignore index eb5a316..2cba583 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -target +target diff --git a/src/communities/interactables/category.rs b/src/communities/interactables/category.rs index 4958735..40ca625 100644 --- a/src/communities/interactables/category.rs +++ b/src/communities/interactables/category.rs @@ -1,123 +1,123 @@ -use crate::{ - communities::{community::Community, interactables::interactable::Interactable}, - data::communication::{CommunicationType, CommunicationValue}, -}; -use async_trait::async_trait; -use json::JsonValue; -use std::any::Any; -use std::sync::Arc; -use uuid::Uuid; - -pub struct Category { - id: Uuid, - name: String, - path: String, - community: Arc, - children: Vec>>, -} -impl Category { - pub fn new() -> Category { - Category { - id: Uuid::new_v4(), - name: String::new(), - path: String::new(), - community: Arc::new(Community::new()), - children: Vec::new(), - } - } - pub fn get_child(&self, path: String, name: String) -> Option>> { - if path.is_empty() { - self.children - .iter() - .find(|child| child.get_name() == &name) - .cloned() - } else { - let sub_module = path.split("/").next().unwrap(); - let next = self - .children - .iter() - .find(|child| child.get_name() == sub_module) - .unwrap(); - if next.get_codec() == "category" { - let next_cat = next.as_any().downcast_ref::().unwrap(); - next_cat.get_child(path, name) - } else { - Some(next.clone()) - } - } - } - pub fn get_children(&self) -> Vec>> { - self.children.iter().map(|child| child.clone()).collect() - } -} - -#[async_trait] -impl Interactable for Category { - fn get_id(&self) -> &Uuid { - &self.id - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - fn get_codec(&self) -> String { - "category".to_string() - } - fn set_name(&mut self, name: String) { - self.name = name; - } - fn set_path(&mut self, path: String) { - self.path = path; - } - fn get_community(&self) -> &Arc { - &self.community - } - fn set_community(&mut self, community: Arc) { - self.community = community; - } - fn get_name(&self) -> &String { - &self.name - } - fn get_path(&self) -> &String { - &self.path - } - fn get_total_path(&self) -> String { - String::new() + &self.path + "/" + &self.name - } - fn get_data(&self) -> JsonValue { - let mut v = JsonValue::new_object(); - for child in &self.children { - let mut subject = JsonValue::new_object(); - subject["codec"] = JsonValue::String(child.get_codec()); - subject["data"] = child.get_data(); - v[child.get_name()] = subject; - } - v - } - async fn run_function(&self, _cv: CommunicationValue) -> CommunicationValue { - CommunicationValue::new(CommunicationType::error) - } - fn to_json(&self) -> JsonValue { - let mut v = JsonValue::new_object(); - v["children"] = JsonValue::new_array(); - for child in &self.children { - let _ = v["children"].push(child.to_json()); - } - v - } - fn load( - &mut self, - community: Arc, - id: Uuid, - path: String, - name: String, - _json: &JsonValue, - ) { - self.community = community; - self.id = id; - self.name = name; - self.path = path; - } -} +use crate::{ + communities::{community::Community, interactables::interactable::Interactable}, + data::communication::{CommunicationType, CommunicationValue}, +}; +use async_trait::async_trait; +use json::JsonValue; +use std::any::Any; +use std::sync::Arc; +use uuid::Uuid; + +pub struct Category { + id: Uuid, + name: String, + path: String, + community: Arc, + children: Vec>>, +} +impl Category { + pub fn new() -> Category { + Category { + id: Uuid::new_v4(), + name: String::new(), + path: String::new(), + community: Arc::new(Community::new()), + children: Vec::new(), + } + } + pub fn get_child(&self, path: String, name: String) -> Option>> { + if path.is_empty() { + self.children + .iter() + .find(|child| child.get_name() == &name) + .cloned() + } else { + let sub_module = path.split("/").next().unwrap(); + let next = self + .children + .iter() + .find(|child| child.get_name() == sub_module) + .unwrap(); + if next.get_codec() == "category" { + let next_cat = next.as_any().downcast_ref::().unwrap(); + next_cat.get_child(path, name) + } else { + Some(next.clone()) + } + } + } + pub fn get_children(&self) -> Vec>> { + self.children.iter().map(|child| child.clone()).collect() + } +} + +#[async_trait] +impl Interactable for Category { + fn get_id(&self) -> &Uuid { + &self.id + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn get_codec(&self) -> String { + "category".to_string() + } + fn set_name(&mut self, name: String) { + self.name = name; + } + fn set_path(&mut self, path: String) { + self.path = path; + } + fn get_community(&self) -> &Arc { + &self.community + } + fn set_community(&mut self, community: Arc) { + self.community = community; + } + fn get_name(&self) -> &String { + &self.name + } + fn get_path(&self) -> &String { + &self.path + } + fn get_total_path(&self) -> String { + String::new() + &self.path + "/" + &self.name + } + fn get_data(&self) -> JsonValue { + let mut v = JsonValue::new_object(); + for child in &self.children { + let mut subject = JsonValue::new_object(); + subject["codec"] = JsonValue::String(child.get_codec()); + subject["data"] = child.get_data(); + v[child.get_name()] = subject; + } + v + } + async fn run_function(&self, _cv: CommunicationValue) -> CommunicationValue { + CommunicationValue::new(CommunicationType::error) + } + fn to_json(&self) -> JsonValue { + let mut v = JsonValue::new_object(); + v["children"] = JsonValue::new_array(); + for child in &self.children { + let _ = v["children"].push(child.to_json()); + } + v + } + fn load( + &mut self, + community: Arc, + id: Uuid, + path: String, + name: String, + _json: &JsonValue, + ) { + self.community = community; + self.id = id; + self.name = name; + self.path = path; + } +} diff --git a/src/communities/interactables/interactable.rs b/src/communities/interactables/interactable.rs index 9c9805c..7d581d0 100644 --- a/src/communities/interactables/interactable.rs +++ b/src/communities/interactables/interactable.rs @@ -1,34 +1,34 @@ -use crate::{communities::community::Community, data::communication::CommunicationValue}; -use async_trait::async_trait; -use json::JsonValue; -use std::any::Any; -use std::sync::Arc; -use uuid::Uuid; - -pub type InteractableFactory = fn() -> Box; - -#[async_trait] -pub trait Interactable: Send + Sync + Any { - fn as_any(&self) -> &dyn Any; - fn as_any_mut(&mut self) -> &mut dyn Any; - fn get_codec(&self) -> String; - fn get_name(&self) -> &String; - fn get_path(&self) -> &String; - fn get_total_path(&self) -> String; - fn set_name(&mut self, name: String); - fn set_path(&mut self, path: String); - fn get_community(&self) -> &Arc; - fn set_community(&mut self, community: Arc); - async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue; - fn get_data(&self) -> JsonValue; - fn get_id(&self) -> &Uuid; - fn to_json(&self) -> JsonValue; - fn load( - &mut self, - community: Arc, - id: Uuid, - path: String, - name: String, - json: &JsonValue, - ); -} +use crate::{communities::community::Community, data::communication::CommunicationValue}; +use async_trait::async_trait; +use json::JsonValue; +use std::any::Any; +use std::sync::Arc; +use uuid::Uuid; + +pub type InteractableFactory = fn() -> Box; + +#[async_trait] +pub trait Interactable: Send + Sync + Any { + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; + fn get_codec(&self) -> String; + fn get_name(&self) -> &String; + fn get_path(&self) -> &String; + fn get_total_path(&self) -> String; + fn set_name(&mut self, name: String); + fn set_path(&mut self, path: String); + fn get_community(&self) -> &Arc; + fn set_community(&mut self, community: Arc); + async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue; + fn get_data(&self) -> JsonValue; + fn get_id(&self) -> &Uuid; + fn to_json(&self) -> JsonValue; + fn load( + &mut self, + community: Arc, + id: Uuid, + path: String, + name: String, + json: &JsonValue, + ); +} diff --git a/src/communities/interactables/registry.rs b/src/communities/interactables/registry.rs index 47fce92..2f57005 100644 --- a/src/communities/interactables/registry.rs +++ b/src/communities/interactables/registry.rs @@ -1,75 +1,75 @@ -use crate::communities::community::Community; -use crate::communities::interactables::category::Category; -use crate::communities::interactables::interactable::{Interactable, InteractableFactory}; -use crate::communities::interactables::text_chat::TextChat; -use crate::communities::interactables::voice_chat::VoiceChat; -use crate::util::file_util; -use json::JsonValue; -use once_cell::sync::Lazy; -use std::collections::HashMap; -use std::sync::Arc; -use tokio::sync::Mutex; -use uuid::Uuid; - -pub static INTERACTABLE_REGISTRY: Lazy>>> = - Lazy::new(|| Arc::new(Mutex::new(HashMap::new()))); -pub async fn load_interactables() { - INTERACTABLE_REGISTRY - .lock() - .await - .insert(TextChat::new().get_codec(), || { - Box::new(TextChat::new()) as Box - }); - INTERACTABLE_REGISTRY - .lock() - .await - .insert(VoiceChat::new().get_codec(), || { - Box::new(VoiceChat::new()) as Box - }); - INTERACTABLE_REGISTRY - .lock() - .await - .insert(Category::new().get_codec(), || { - Box::new(Category::new()) as Box - }); -} -pub async fn register_interactable(name: String, interactable: InteractableFactory) { - INTERACTABLE_REGISTRY - .lock() - .await - .insert(name.to_string(), interactable) - .unwrap(); -} -pub async fn get_interactable(name: &str) -> Box { - INTERACTABLE_REGISTRY.lock().await.get(name).unwrap()() -} -pub async fn save(interactable: &Arc>) { - let mut json_object: JsonValue = interactable.to_json().clone(); - json_object["codec"] = JsonValue::String(interactable.get_codec()); - json_object["id"] = JsonValue::String(interactable.get_id().to_string()); - file_util::save_file( - &format!( - "communities/{}/interactables/{}", - interactable.get_community().get_name(), - interactable.get_path() - ), - &format!("{}.json", interactable.get_name()), - &json_object.to_string(), - ); -} -pub async fn load( - c: Arc, - path: String, - name: String, -) -> Box { - let s = file_util::load_file( - &format!("communities/{}/interactables/{}", c.get_name(), path), - &format!("{}.json", name), - ); - let json_object: JsonValue = json::parse(&s).unwrap(); - let codec: String = json_object["codec"].as_str().unwrap().to_string(); - let id: String = json_object["id"].as_str().unwrap().to_string(); - let mut interactable = get_interactable(&codec).await; - interactable.load(c, Uuid::parse_str(&id).unwrap(), path, name, &json_object); - interactable -} +use crate::communities::community::Community; +use crate::communities::interactables::category::Category; +use crate::communities::interactables::interactable::{Interactable, InteractableFactory}; +use crate::communities::interactables::text_chat::TextChat; +use crate::communities::interactables::voice_chat::VoiceChat; +use crate::util::file_util; +use json::JsonValue; +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::Mutex; +use uuid::Uuid; + +pub static INTERACTABLE_REGISTRY: Lazy>>> = + Lazy::new(|| Arc::new(Mutex::new(HashMap::new()))); +pub async fn load_interactables() { + INTERACTABLE_REGISTRY + .lock() + .await + .insert(TextChat::new().get_codec(), || { + Box::new(TextChat::new()) as Box + }); + INTERACTABLE_REGISTRY + .lock() + .await + .insert(VoiceChat::new().get_codec(), || { + Box::new(VoiceChat::new()) as Box + }); + INTERACTABLE_REGISTRY + .lock() + .await + .insert(Category::new().get_codec(), || { + Box::new(Category::new()) as Box + }); +} +pub async fn register_interactable(name: String, interactable: InteractableFactory) { + INTERACTABLE_REGISTRY + .lock() + .await + .insert(name.to_string(), interactable) + .unwrap(); +} +pub async fn get_interactable(name: &str) -> Box { + INTERACTABLE_REGISTRY.lock().await.get(name).unwrap()() +} +pub async fn save(interactable: &Arc>) { + let mut json_object: JsonValue = interactable.to_json().clone(); + json_object["codec"] = JsonValue::String(interactable.get_codec()); + json_object["id"] = JsonValue::String(interactable.get_id().to_string()); + file_util::save_file( + &format!( + "communities/{}/interactables/{}", + interactable.get_community().get_name(), + interactable.get_path() + ), + &format!("{}.json", interactable.get_name()), + &json_object.to_string(), + ); +} +pub async fn load( + c: Arc, + path: String, + name: String, +) -> Box { + let s = file_util::load_file( + &format!("communities/{}/interactables/{}", c.get_name(), path), + &format!("{}.json", name), + ); + let json_object: JsonValue = json::parse(&s).unwrap(); + let codec: String = json_object["codec"].as_str().unwrap().to_string(); + let id: String = json_object["id"].as_str().unwrap().to_string(); + let mut interactable = get_interactable(&codec).await; + interactable.load(c, Uuid::parse_str(&id).unwrap(), path, name, &json_object); + interactable +} diff --git a/src/communities/interactables/text_chat.rs b/src/communities/interactables/text_chat.rs index fd11e2e..8200afd 100644 --- a/src/communities/interactables/text_chat.rs +++ b/src/communities/interactables/text_chat.rs @@ -1,262 +1,262 @@ -use crate::{ - communities::{ - community::Community, community_connection::CommunityConnection, - interactables::interactable::Interactable, - }, - data::communication::{CommunicationType, CommunicationValue, DataTypes}, - gui::log_panel::log_message, - util::file_util::{get_children, load_file, save_file}, -}; -use async_trait::async_trait; -use json::{JsonValue, array, object}; -use std::fs; -use std::sync::Arc; -use std::{any::Any, collections::HashMap}; -use uuid::Uuid; -pub struct TextChat { - id: Uuid, - name: String, - path: String, - community: Arc, -} -impl TextChat { - pub fn new() -> TextChat { - TextChat { - id: Uuid::new_v4(), - name: String::new(), - path: String::new(), - community: Arc::new(Community::new()), - } - } - pub fn add_message(&self, send_time: u128, sender: i64, message: &str) { - let user_dir = &format!( - "communities/{}/interactables/{}/{}", - self.get_community().get_name(), - self.get_path(), - self.get_name() - ); - - if let Err(e) = fs::create_dir_all(user_dir) { - log_message(format!("Failed to create chat directory: {}", e)); - return; - } - - let mut chunk_index = 0; - let mut message_chunk = array![]; - - // find latest chunk not full (max 800 msgs) - loop { - let file_name = format!("msgs_{}.json", chunk_index); - let file_content = load_file(&user_dir, &file_name); - - if !file_content.is_empty() { - if let Ok(current_chunk) = json::parse(&file_content) { - if current_chunk.is_array() && current_chunk.len() < 800 { - message_chunk = current_chunk; - break; - } - } else { - log_message(format!("Failed to parse existing JSON file: {}", file_name)); - } - } else { - // New file, use empty array - break; - } - - chunk_index += 1; - if chunk_index > 1000 { - log_message(format!("Too many message chunks. Aborting add.")); - return; - } - } - - let json_obj = object! { - "timestamp" => send_time as i64, - "content" => message, - "sender" => sender.to_string(), - }; - - if let Err(e) = message_chunk.push(json_obj) { - log_message(format!("Failed to push new message into JSON array: {}", e)); - return; - } - - let file_name = format!("msgs_{}.json", chunk_index); - log_message(format!("Saving message to {}/{}", user_dir, file_name)); - save_file(&user_dir, &file_name, &message_chunk.dump()); - } - pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue { - let mut messages = array![]; - - let mut latest_chunk_index: i32 = -1; - let files = get_children(&format!( - "communities/{}/interactables/{}/{}", - self.get_community().get_name(), - self.get_path(), - self.get_name() - )); - - for entry in files { - if let Some(num) = { - entry - .strip_prefix("msgs_") - .and_then(|s| s.strip_suffix(".json")) - } { - if let Ok(index) = num.parse::() { - if index > latest_chunk_index { - latest_chunk_index = index; - } - } - } - } - - if latest_chunk_index == -1 { - return messages; - } - - let mut to_skip = loaded_messages; - let mut needed = amount; - - for chunk_index in (0..=latest_chunk_index).rev() { - if needed == 0 { - break; - } - let file_name = format!("msgs_{}.json", chunk_index); - let file_content = load_file( - &format!( - "communities/{}/interactables/{}/{}", - self.get_community().get_name(), - self.get_path(), - self.get_name() - ), - &file_name, - ); - if file_content.is_empty() { - continue; - } - if let Ok(chunk) = json::parse(&file_content) { - for i in (0..chunk.len()).rev() { - if needed == 0 { - break; - } - if to_skip > 0 { - to_skip -= 1; - continue; - } - messages.push(chunk[i].clone()).unwrap(); - needed -= 1; - } - } - } - - messages - } -} -#[async_trait] -impl Interactable for TextChat { - fn get_id(&self) -> &Uuid { - &self.id - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - fn get_codec(&self) -> String { - "text".to_string() - } - fn set_name(&mut self, name: String) { - self.name = name; - } - fn set_path(&mut self, path: String) { - self.path = path; - } - fn get_community(&self) -> &Arc { - &self.community - } - fn set_community(&mut self, community: Arc) { - self.community = community; - } - fn get_name(&self) -> &String { - &self.name - } - fn get_path(&self) -> &String { - &self.path - } - fn get_total_path(&self) -> String { - String::new() + &self.path + "/" + &self.name - } - fn get_data(&self) -> JsonValue { - JsonValue::new_object() - } - async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue { - let payload = cv.get_data(DataTypes::payload).unwrap(); - if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "get_messages" { - let amount = payload["amount"].as_i64().unwrap(); - let loaded_messages = payload["loaded_messages"].as_i64().unwrap(); - let messages = self.get_messages(loaded_messages, amount).clone(); - let mut payload = JsonValue::new_object(); - payload["messages"] = messages; - return CommunicationValue::new(CommunicationType::function) - .with_id(cv.get_id()) - .add_data_str(DataTypes::name, self.name.clone()) - .add_data_str(DataTypes::path, self.path.clone()) - .add_data_str(DataTypes::result, "message_chunk".to_string()) - .add_data(DataTypes::payload, payload); - } - if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "send_message" { - let message = payload["message"].as_str().unwrap(); - let milliseconds_timestamp: u128 = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap() - .as_millis(); - self.add_message(milliseconds_timestamp, cv.get_sender(), message); - - let mut distribution_payload = JsonValue::new_object(); - distribution_payload["message"] = JsonValue::String(message.to_string()); - distribution_payload["sender_id"] = JsonValue::String(cv.get_sender().to_string()); - distribution_payload["send_time"] = - JsonValue::String(milliseconds_timestamp.to_string()); - let distribution = CommunicationValue::new(CommunicationType::update) - .with_id(cv.get_id()) - .add_data_str(DataTypes::name, self.name.clone()) - .add_data_str(DataTypes::path, self.path.clone()) - .add_data_str(DataTypes::result, "message_live".to_string()) - .add_data(DataTypes::payload, distribution_payload); - - let connections: HashMap>> = - self.get_community().get_connections().await.clone(); - - for con in connections.values() { - for c in con { - let cd: &Arc = c; - cd.send_message(&distribution).await; - } - } - return CommunicationValue::new(CommunicationType::function) - .with_id(cv.get_id()) - .add_data_str(DataTypes::name, self.name.clone()) - .add_data_str(DataTypes::path, self.path.clone()) - .add_data_str(DataTypes::result, "message_received".to_string()) - .add_data(DataTypes::payload, JsonValue::new_object()); - } - CommunicationValue::new(CommunicationType::error).with_id(cv.get_id()) - } - fn to_json(&self) -> JsonValue { - JsonValue::new_object() - } - fn load( - &mut self, - community: Arc, - id: Uuid, - path: String, - name: String, - _json: &JsonValue, - ) { - self.community = community; - self.id = id; - self.name = name; - self.path = path; - } -} +use crate::{ + communities::{ + community::Community, community_connection::CommunityConnection, + interactables::interactable::Interactable, + }, + data::communication::{CommunicationType, CommunicationValue, DataTypes}, + gui::log_panel::log_message, + util::file_util::{get_children, load_file, save_file}, +}; +use async_trait::async_trait; +use json::{JsonValue, array, object}; +use std::fs; +use std::sync::Arc; +use std::{any::Any, collections::HashMap}; +use uuid::Uuid; +pub struct TextChat { + id: Uuid, + name: String, + path: String, + community: Arc, +} +impl TextChat { + pub fn new() -> TextChat { + TextChat { + id: Uuid::new_v4(), + name: String::new(), + path: String::new(), + community: Arc::new(Community::new()), + } + } + pub fn add_message(&self, send_time: u128, sender: i64, message: &str) { + let user_dir = &format!( + "communities/{}/interactables/{}/{}", + self.get_community().get_name(), + self.get_path(), + self.get_name() + ); + + if let Err(e) = fs::create_dir_all(user_dir) { + log_message(format!("Failed to create chat directory: {}", e)); + return; + } + + let mut chunk_index = 0; + let mut message_chunk = array![]; + + // find latest chunk not full (max 800 msgs) + loop { + let file_name = format!("msgs_{}.json", chunk_index); + let file_content = load_file(&user_dir, &file_name); + + if !file_content.is_empty() { + if let Ok(current_chunk) = json::parse(&file_content) { + if current_chunk.is_array() && current_chunk.len() < 800 { + message_chunk = current_chunk; + break; + } + } else { + log_message(format!("Failed to parse existing JSON file: {}", file_name)); + } + } else { + // New file, use empty array + break; + } + + chunk_index += 1; + if chunk_index > 1000 { + log_message(format!("Too many message chunks. Aborting add.")); + return; + } + } + + let json_obj = object! { + "timestamp" => send_time as i64, + "content" => message, + "sender" => sender.to_string(), + }; + + if let Err(e) = message_chunk.push(json_obj) { + log_message(format!("Failed to push new message into JSON array: {}", e)); + return; + } + + let file_name = format!("msgs_{}.json", chunk_index); + log_message(format!("Saving message to {}/{}", user_dir, file_name)); + save_file(&user_dir, &file_name, &message_chunk.dump()); + } + pub fn get_messages(&self, loaded_messages: i64, amount: i64) -> JsonValue { + let mut messages = array![]; + + let mut latest_chunk_index: i32 = -1; + let files = get_children(&format!( + "communities/{}/interactables/{}/{}", + self.get_community().get_name(), + self.get_path(), + self.get_name() + )); + + for entry in files { + if let Some(num) = { + entry + .strip_prefix("msgs_") + .and_then(|s| s.strip_suffix(".json")) + } { + if let Ok(index) = num.parse::() { + if index > latest_chunk_index { + latest_chunk_index = index; + } + } + } + } + + if latest_chunk_index == -1 { + return messages; + } + + let mut to_skip = loaded_messages; + let mut needed = amount; + + for chunk_index in (0..=latest_chunk_index).rev() { + if needed == 0 { + break; + } + let file_name = format!("msgs_{}.json", chunk_index); + let file_content = load_file( + &format!( + "communities/{}/interactables/{}/{}", + self.get_community().get_name(), + self.get_path(), + self.get_name() + ), + &file_name, + ); + if file_content.is_empty() { + continue; + } + if let Ok(chunk) = json::parse(&file_content) { + for i in (0..chunk.len()).rev() { + if needed == 0 { + break; + } + if to_skip > 0 { + to_skip -= 1; + continue; + } + messages.push(chunk[i].clone()).unwrap(); + needed -= 1; + } + } + } + + messages + } +} +#[async_trait] +impl Interactable for TextChat { + fn get_id(&self) -> &Uuid { + &self.id + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn get_codec(&self) -> String { + "text".to_string() + } + fn set_name(&mut self, name: String) { + self.name = name; + } + fn set_path(&mut self, path: String) { + self.path = path; + } + fn get_community(&self) -> &Arc { + &self.community + } + fn set_community(&mut self, community: Arc) { + self.community = community; + } + fn get_name(&self) -> &String { + &self.name + } + fn get_path(&self) -> &String { + &self.path + } + fn get_total_path(&self) -> String { + String::new() + &self.path + "/" + &self.name + } + fn get_data(&self) -> JsonValue { + JsonValue::new_object() + } + async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue { + let payload = cv.get_data(DataTypes::payload).unwrap(); + if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "get_messages" { + let amount = payload["amount"].as_i64().unwrap(); + let loaded_messages = payload["loaded_messages"].as_i64().unwrap(); + let messages = self.get_messages(loaded_messages, amount).clone(); + let mut payload = JsonValue::new_object(); + payload["messages"] = messages; + return CommunicationValue::new(CommunicationType::function) + .with_id(cv.get_id()) + .add_data_str(DataTypes::name, self.name.clone()) + .add_data_str(DataTypes::path, self.path.clone()) + .add_data_str(DataTypes::result, "message_chunk".to_string()) + .add_data(DataTypes::payload, payload); + } + if cv.get_data(DataTypes::function).unwrap().as_str().unwrap() == "send_message" { + let message = payload["message"].as_str().unwrap(); + let milliseconds_timestamp: u128 = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_millis(); + self.add_message(milliseconds_timestamp, cv.get_sender(), message); + + let mut distribution_payload = JsonValue::new_object(); + distribution_payload["message"] = JsonValue::String(message.to_string()); + distribution_payload["sender_id"] = JsonValue::String(cv.get_sender().to_string()); + distribution_payload["send_time"] = + JsonValue::String(milliseconds_timestamp.to_string()); + let distribution = CommunicationValue::new(CommunicationType::update) + .with_id(cv.get_id()) + .add_data_str(DataTypes::name, self.name.clone()) + .add_data_str(DataTypes::path, self.path.clone()) + .add_data_str(DataTypes::result, "message_live".to_string()) + .add_data(DataTypes::payload, distribution_payload); + + let connections: HashMap>> = + self.get_community().get_connections().await.clone(); + + for con in connections.values() { + for c in con { + let cd: &Arc = c; + cd.send_message(&distribution).await; + } + } + return CommunicationValue::new(CommunicationType::function) + .with_id(cv.get_id()) + .add_data_str(DataTypes::name, self.name.clone()) + .add_data_str(DataTypes::path, self.path.clone()) + .add_data_str(DataTypes::result, "message_received".to_string()) + .add_data(DataTypes::payload, JsonValue::new_object()); + } + CommunicationValue::new(CommunicationType::error).with_id(cv.get_id()) + } + fn to_json(&self) -> JsonValue { + JsonValue::new_object() + } + fn load( + &mut self, + community: Arc, + id: Uuid, + path: String, + name: String, + _json: &JsonValue, + ) { + self.community = community; + self.id = id; + self.name = name; + self.path = path; + } +} diff --git a/src/communities/interactables/voice_chat.rs b/src/communities/interactables/voice_chat.rs index ad0b6ab..c452ea1 100644 --- a/src/communities/interactables/voice_chat.rs +++ b/src/communities/interactables/voice_chat.rs @@ -1,190 +1,190 @@ -use crate::{ - communities::{community::Community, interactables::interactable::Interactable}, - data::communication::{CommunicationType, CommunicationValue, DataTypes}, -}; -use async_trait::async_trait; -use json::JsonValue; -use std::sync::Arc; -use std::{any::Any, sync::RwLock}; -use uuid::Uuid; -pub enum CallUserState { - Active, - Muted, - Deafed, -} -impl CallUserState { - pub fn parse(state: &str) -> CallUserState { - match state { - "active" => CallUserState::Active, - "muted" => CallUserState::Muted, - "deafed" => CallUserState::Deafed, - _ => CallUserState::Active, - } - } - pub fn to_string(&self) -> String { - match self { - CallUserState::Active => "active".to_string(), - CallUserState::Muted => "muted".to_string(), - CallUserState::Deafed => "deafed".to_string(), - } - } -} - -pub struct CallUser { - pub user_id: Uuid, - pub user_state: CallUserState, - pub streaming: bool, -} - -pub struct VoiceChat { - id: Uuid, - name: String, - path: String, - community: Arc, - users: RwLock>, -} -impl VoiceChat { - pub fn new() -> VoiceChat { - VoiceChat { - id: Uuid::new_v4(), - name: String::new(), - path: String::new(), - community: Arc::new(Community::new()), - users: RwLock::new(Vec::new()), - } - } - pub fn update_user_state( - self: Arc, - user_id: Uuid, - state: CallUserState, - streaming: bool, - ) { - if let Some(user) = self - .users - .write() - .unwrap() - .iter_mut() - .find(|u| u.user_id == user_id) - { - user.user_state = state; - user.streaming = streaming; - } - } -} -#[async_trait] -impl Interactable for VoiceChat { - fn get_id(&self) -> &Uuid { - &self.id - } - fn as_any(&self) -> &dyn Any { - self - } - fn as_any_mut(&mut self) -> &mut dyn Any { - self - } - fn get_codec(&self) -> String { - "voice".to_string() - } - fn set_name(&mut self, name: String) { - self.name = name; - } - fn set_path(&mut self, path: String) { - self.path = path; - } - fn get_community(&self) -> &Arc { - &self.community - } - fn set_community(&mut self, community: Arc) { - self.community = community; - } - fn get_name(&self) -> &String { - &self.name - } - fn get_path(&self) -> &String { - &self.path - } - fn get_total_path(&self) -> String { - String::new() + &self.path + "/" + &self.name - } - fn get_data(&self) -> JsonValue { - let mut data = JsonValue::new_object(); - let mut active_users = JsonValue::new_object(); - for user in self.users.read().unwrap().iter() { - let mut user_data = JsonValue::new_object(); - let _ = user_data.insert("state", JsonValue::String(user.user_state.to_string())); - let _ = user_data.insert("streaming", JsonValue::Boolean(user.streaming)); - let _ = active_users.insert(&user.user_id.to_string(), user_data); - } - let _ = data.insert("active_users", active_users); - data - } - async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue { - let payload = cv.get_data(DataTypes::payload).unwrap(); - let function = cv.get_data(DataTypes::function).unwrap().as_str().unwrap(); - - if function == "get_call" { - let sender_id = payload["sender_id"].as_str().unwrap(); - let message_id = payload["message"].as_str().unwrap(); - let send_time = payload["send_time"].as_str().unwrap(); - - let mut response_payload = JsonValue::new_object(); - response_payload["sender_id"] = JsonValue::String(sender_id.to_string()); - response_payload["message"] = JsonValue::String(message_id.to_string()); - response_payload["send_time"] = JsonValue::String(send_time.to_string()); - - return CommunicationValue::new(CommunicationType::function) - .with_id(cv.get_id()) - .add_data_str(DataTypes::name, self.name.clone()) - .add_data_str(DataTypes::path, self.path.clone()) - .add_data_str(DataTypes::result, "getting_call".to_string()) - .add_data(DataTypes::payload, response_payload); - } - - if function == "update_user_state" { - let user_id = payload["user_id"].as_str().unwrap(); - let state = payload["state"].as_str().unwrap(); - let streaming = payload["streaming"].as_bool().unwrap(); - - if let Some(user) = self - .users - .write() - .unwrap() - .iter_mut() - .find(|u| u.user_id == Uuid::parse_str(user_id).unwrap()) - { - user.user_state = CallUserState::parse(state); - user.streaming = streaming; - } - let mut response_payload = JsonValue::new_object(); - response_payload["user_id"] = JsonValue::String(user_id.to_string()); - response_payload["state"] = JsonValue::String(state.to_string()); - response_payload["streaming"] = JsonValue::Boolean(streaming); - - return CommunicationValue::new(CommunicationType::update) - .with_id(cv.get_id()) - .add_data_str(DataTypes::name, self.name.clone()) - .add_data_str(DataTypes::path, self.path.clone()) - .add_data_str(DataTypes::result, "user_changed".to_string()) - .add_data(DataTypes::payload, response_payload); - } - CommunicationValue::new(CommunicationType::error).with_id(cv.get_id()) - } - - fn to_json(&self) -> JsonValue { - let v = JsonValue::new_object(); - v - } - fn load( - &mut self, - community: Arc, - id: Uuid, - path: String, - name: String, - _json: &JsonValue, - ) { - self.community = community; - self.id = id; - self.name = name; - self.path = path; - } -} +use crate::{ + communities::{community::Community, interactables::interactable::Interactable}, + data::communication::{CommunicationType, CommunicationValue, DataTypes}, +}; +use async_trait::async_trait; +use json::JsonValue; +use std::sync::Arc; +use std::{any::Any, sync::RwLock}; +use uuid::Uuid; +pub enum CallUserState { + Active, + Muted, + Deafed, +} +impl CallUserState { + pub fn parse(state: &str) -> CallUserState { + match state { + "active" => CallUserState::Active, + "muted" => CallUserState::Muted, + "deafed" => CallUserState::Deafed, + _ => CallUserState::Active, + } + } + pub fn to_string(&self) -> String { + match self { + CallUserState::Active => "active".to_string(), + CallUserState::Muted => "muted".to_string(), + CallUserState::Deafed => "deafed".to_string(), + } + } +} + +pub struct CallUser { + pub user_id: Uuid, + pub user_state: CallUserState, + pub streaming: bool, +} + +pub struct VoiceChat { + id: Uuid, + name: String, + path: String, + community: Arc, + users: RwLock>, +} +impl VoiceChat { + pub fn new() -> VoiceChat { + VoiceChat { + id: Uuid::new_v4(), + name: String::new(), + path: String::new(), + community: Arc::new(Community::new()), + users: RwLock::new(Vec::new()), + } + } + pub fn update_user_state( + self: Arc, + user_id: Uuid, + state: CallUserState, + streaming: bool, + ) { + if let Some(user) = self + .users + .write() + .unwrap() + .iter_mut() + .find(|u| u.user_id == user_id) + { + user.user_state = state; + user.streaming = streaming; + } + } +} +#[async_trait] +impl Interactable for VoiceChat { + fn get_id(&self) -> &Uuid { + &self.id + } + fn as_any(&self) -> &dyn Any { + self + } + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + fn get_codec(&self) -> String { + "voice".to_string() + } + fn set_name(&mut self, name: String) { + self.name = name; + } + fn set_path(&mut self, path: String) { + self.path = path; + } + fn get_community(&self) -> &Arc { + &self.community + } + fn set_community(&mut self, community: Arc) { + self.community = community; + } + fn get_name(&self) -> &String { + &self.name + } + fn get_path(&self) -> &String { + &self.path + } + fn get_total_path(&self) -> String { + String::new() + &self.path + "/" + &self.name + } + fn get_data(&self) -> JsonValue { + let mut data = JsonValue::new_object(); + let mut active_users = JsonValue::new_object(); + for user in self.users.read().unwrap().iter() { + let mut user_data = JsonValue::new_object(); + let _ = user_data.insert("state", JsonValue::String(user.user_state.to_string())); + let _ = user_data.insert("streaming", JsonValue::Boolean(user.streaming)); + let _ = active_users.insert(&user.user_id.to_string(), user_data); + } + let _ = data.insert("active_users", active_users); + data + } + async fn run_function(&self, cv: CommunicationValue) -> CommunicationValue { + let payload = cv.get_data(DataTypes::payload).unwrap(); + let function = cv.get_data(DataTypes::function).unwrap().as_str().unwrap(); + + if function == "get_call" { + let sender_id = payload["sender_id"].as_str().unwrap(); + let message_id = payload["message"].as_str().unwrap(); + let send_time = payload["send_time"].as_str().unwrap(); + + let mut response_payload = JsonValue::new_object(); + response_payload["sender_id"] = JsonValue::String(sender_id.to_string()); + response_payload["message"] = JsonValue::String(message_id.to_string()); + response_payload["send_time"] = JsonValue::String(send_time.to_string()); + + return CommunicationValue::new(CommunicationType::function) + .with_id(cv.get_id()) + .add_data_str(DataTypes::name, self.name.clone()) + .add_data_str(DataTypes::path, self.path.clone()) + .add_data_str(DataTypes::result, "getting_call".to_string()) + .add_data(DataTypes::payload, response_payload); + } + + if function == "update_user_state" { + let user_id = payload["user_id"].as_str().unwrap(); + let state = payload["state"].as_str().unwrap(); + let streaming = payload["streaming"].as_bool().unwrap(); + + if let Some(user) = self + .users + .write() + .unwrap() + .iter_mut() + .find(|u| u.user_id == Uuid::parse_str(user_id).unwrap()) + { + user.user_state = CallUserState::parse(state); + user.streaming = streaming; + } + let mut response_payload = JsonValue::new_object(); + response_payload["user_id"] = JsonValue::String(user_id.to_string()); + response_payload["state"] = JsonValue::String(state.to_string()); + response_payload["streaming"] = JsonValue::Boolean(streaming); + + return CommunicationValue::new(CommunicationType::update) + .with_id(cv.get_id()) + .add_data_str(DataTypes::name, self.name.clone()) + .add_data_str(DataTypes::path, self.path.clone()) + .add_data_str(DataTypes::result, "user_changed".to_string()) + .add_data(DataTypes::payload, response_payload); + } + CommunicationValue::new(CommunicationType::error).with_id(cv.get_id()) + } + + fn to_json(&self) -> JsonValue { + let v = JsonValue::new_object(); + v + } + fn load( + &mut self, + community: Arc, + id: Uuid, + path: String, + name: String, + _json: &JsonValue, + ) { + self.community = community; + self.id = id; + self.name = name; + self.path = path; + } +} diff --git a/src/communities/mod.rs b/src/communities/mod.rs index 237b709..ecad116 100644 --- a/src/communities/mod.rs +++ b/src/communities/mod.rs @@ -1,13 +1,13 @@ -pub mod community_manager; -pub mod interactables { - pub mod category; - pub mod interactable; - pub mod registry; - pub mod text_chat; - pub mod voice_chat; -} -pub mod community; -pub mod community_connection; -pub mod perms { - pub mod permission; -} +pub mod community_manager; +pub mod interactables { + pub mod category; + pub mod interactable; + pub mod registry; + pub mod text_chat; + pub mod voice_chat; +} +pub mod community; +pub mod community_connection; +pub mod perms { + pub mod permission; +} diff --git a/src/communities/perms/permission.rs b/src/communities/perms/permission.rs index 50d1b27..32a37ee 100644 --- a/src/communities/perms/permission.rs +++ b/src/communities/perms/permission.rs @@ -1,27 +1,27 @@ -use json::JsonValue; -use uuid::Uuid; - -pub struct Permission { - pub id: Uuid, - pub name: String, -} -impl Permission { - pub fn new(id: Uuid, name: String) -> Self { - Permission { id, name } - } - pub fn to_json(&self) -> JsonValue { - json::object! { - "id" => self.id.to_string(), - "name" => self.name.clone() - } - } - pub fn from_json(json: JsonValue) -> Self { - Permission { - id: Uuid::parse_str(json["id"].as_str().unwrap()).unwrap(), - name: json["name"].as_str().unwrap().to_string(), - } - } - pub fn to_string(&self) -> String { - self.to_json().as_str().unwrap().to_string() - } -} +use json::JsonValue; +use uuid::Uuid; + +pub struct Permission { + pub id: Uuid, + pub name: String, +} +impl Permission { + pub fn new(id: Uuid, name: String) -> Self { + Permission { id, name } + } + pub fn to_json(&self) -> JsonValue { + json::object! { + "id" => self.id.to_string(), + "name" => self.name.clone() + } + } + pub fn from_json(json: JsonValue) -> Self { + Permission { + id: Uuid::parse_str(json["id"].as_str().unwrap()).unwrap(), + name: json["name"].as_str().unwrap().to_string(), + } + } + pub fn to_string(&self) -> String { + self.to_json().as_str().unwrap().to_string() + } +} 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/eula/eula_checker.rs b/src/eula/eula_checker.rs index 8e1798f..a1d37fa 100644 --- a/src/eula/eula_checker.rs +++ b/src/eula/eula_checker.rs @@ -1,26 +1,26 @@ -use crate::util::file_util::{load_file, save_file}; - -pub fn check_eula() -> bool { - let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\ - \nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\ - \neula=false"; - let file = load_file("", "eula.txt"); - if file.is_empty() { - save_file("", "eula.txt", eula); - return false; - } - - if file.contains("eula=false") { - false - } else if file.contains("eula=true") { - true - } else { - false - } -} -pub fn accept_eula() { - let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\ - \nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\ - \neula=true"; - save_file("", "eula.txt", eula); -} +use crate::util::file_util::{load_file, save_file}; + +pub fn check_eula() -> bool { + let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\ + \nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\ + \neula=false"; + let file = load_file("", "eula.txt"); + if file.is_empty() { + save_file("", "eula.txt", eula); + return false; + } + + if file.contains("eula=false") { + false + } else if file.contains("eula=true") { + true + } else { + false + } +} +pub fn accept_eula() { + let eula = "By changing the value to \"true\" you agree to our end user license agreement and our terms of service!\ + \nYou can find our Terms of service on https://docs.tensamin.net/legal/terms-of-service/.\ + \neula=true"; + save_file("", "eula.txt", eula); +} diff --git a/src/gui/log_panel.rs b/src/gui/log_panel.rs index 85a9b68..5a3e753 100644 --- a/src/gui/log_panel.rs +++ b/src/gui/log_panel.rs @@ -1,108 +1,108 @@ -use crate::ACTIVE_TASKS; -use crate::APP_STATE; -use crate::SHUTDOWN; -use crate::gui::tui::UNIQUE; -use crate::langu::language_manager::format; -use crate::langu::language_manager::from_key; - -use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; - -use std::{thread, time::Duration}; -use sysinfo::{RefreshKind, System}; - -pub fn log_cv(cv: &CommunicationValue) { - if cv.is_type(CommunicationType::identification_response) { - let args = [cv.get_data(DataTypes::accepted).unwrap().as_str().unwrap()]; - log_message(format(&"identification_response", &args)); - } else { - log_message_trans(format!("{:?}", &cv.comm_type)); - } - tokio::spawn(async move { - *UNIQUE.write().await = true; - }); -} -pub fn log_message_trans(key: impl Into) { - APP_STATE.lock().unwrap().push_log(from_key(&key.into())); - tokio::spawn(async move { - *UNIQUE.write().await = true; - }); -} -pub fn log_message(msg: impl Into) { - APP_STATE.lock().unwrap().push_log(msg.into()); - tokio::spawn(async move { - *UNIQUE.write().await = true; - }); -} -pub fn log_message_format(msg: impl Into, args: &[&str]) { - APP_STATE - .lock() - .unwrap() - .push_log(format(&msg.into(), args)); - tokio::spawn(async move { - *UNIQUE.write().await = true; - }); -} - -pub fn setup() { - tokio::spawn(async move { - { - ACTIVE_TASKS.lock().unwrap().push("metrics".to_string()); - } - let mut sys = System::new_with_specifics(RefreshKind::new()); - let mut last_total_received = 0u64; - let mut last_total_transmitted = 0u64; - let mut counter = 0.0; - loop { - if *SHUTDOWN.read().await { - break; - } - sys.refresh_all(); - - let mut tcpu = 0; - for cpu in sys.cpus() { - tcpu += cpu.cpu_usage() as i64; - tcpu /= 2; - } - let ram = (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0; - - let total_received = 0u64; - let total_transmitted = 0u64; - - let delta_received = if last_total_received == 0 { - 0 - } else { - total_received.saturating_sub(last_total_received) - }; - let delta_transmitted = if last_total_transmitted == 0 { - 0 - } else { - total_transmitted.saturating_sub(last_total_transmitted) - }; - last_total_received = total_received; - last_total_transmitted = total_transmitted; - - let net_down = delta_received as f64; - let net_up = delta_transmitted as f64; - - { - let mut st = APP_STATE.lock().unwrap(); - st.push_cpu((counter, tcpu as f64)); - st.push_ram((counter, ram)); - st.push_net_down((counter, net_down)); - st.push_net_up((counter, net_up)); - - st.sys_info = format!("NetDown: {} NetUp: {}", delta_received, delta_transmitted); - } - - counter += 1.0; - *UNIQUE.write().await = true; - thread::sleep(Duration::from_millis(1000)); - } - { - ACTIVE_TASKS - .lock() - .unwrap() - .retain(|t| !t.eq(&"metrics".to_string())); - } - }); -} +use crate::ACTIVE_TASKS; +use crate::APP_STATE; +use crate::SHUTDOWN; +use crate::gui::tui::UNIQUE; +use crate::langu::language_manager::format; +use crate::langu::language_manager::from_key; + +use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes}; + +use std::{thread, time::Duration}; +use sysinfo::{RefreshKind, System}; + +pub fn log_cv(cv: &CommunicationValue) { + if cv.is_type(CommunicationType::identification_response) { + let args = [cv.get_data(DataTypes::accepted).unwrap().as_str().unwrap()]; + log_message(format(&"identification_response", &args)); + } else { + log_message_trans(format!("{:?}", &cv.get_type())); + } + tokio::spawn(async move { + *UNIQUE.write().await = true; + }); +} +pub fn log_message_trans(key: impl Into) { + APP_STATE.lock().unwrap().push_log(from_key(&key.into())); + tokio::spawn(async move { + *UNIQUE.write().await = true; + }); +} +pub fn log_message(msg: impl Into) { + APP_STATE.lock().unwrap().push_log(msg.into()); + tokio::spawn(async move { + *UNIQUE.write().await = true; + }); +} +pub fn log_message_format(msg: impl Into, args: &[&str]) { + APP_STATE + .lock() + .unwrap() + .push_log(format(&msg.into(), args)); + tokio::spawn(async move { + *UNIQUE.write().await = true; + }); +} + +pub fn setup() { + tokio::spawn(async move { + { + ACTIVE_TASKS.lock().unwrap().push("metrics".to_string()); + } + let mut sys = System::new_with_specifics(RefreshKind::new()); + let mut last_total_received = 0u64; + let mut last_total_transmitted = 0u64; + let mut counter = 0.0; + loop { + if *SHUTDOWN.read().await { + break; + } + sys.refresh_all(); + + let mut tcpu = 0; + for cpu in sys.cpus() { + tcpu += cpu.cpu_usage() as i64; + tcpu /= 2; + } + let ram = (sys.used_memory() as f64 / sys.total_memory() as f64) * 100.0; + + let total_received = 0u64; + let total_transmitted = 0u64; + + let delta_received = if last_total_received == 0 { + 0 + } else { + total_received.saturating_sub(last_total_received) + }; + let delta_transmitted = if last_total_transmitted == 0 { + 0 + } else { + total_transmitted.saturating_sub(last_total_transmitted) + }; + last_total_received = total_received; + last_total_transmitted = total_transmitted; + + let net_down = delta_received as f64; + let net_up = delta_transmitted as f64; + + { + let mut st = APP_STATE.lock().unwrap(); + st.push_cpu((counter, tcpu as f64)); + st.push_ram((counter, ram)); + st.push_net_down((counter, net_down)); + st.push_net_up((counter, net_up)); + + st.sys_info = format!("NetDown: {} NetUp: {}", delta_received, delta_transmitted); + } + + counter += 1.0; + *UNIQUE.write().await = true; + thread::sleep(Duration::from_millis(1000)); + } + { + ACTIVE_TASKS + .lock() + .unwrap() + .retain(|t| !t.eq(&"metrics".to_string())); + } + }); +} diff --git a/src/omikron/omikron_connection.rs b/src/omikron/omikron_connection.rs index 140f1b2..0a5b341 100644 --- a/src/omikron/omikron_connection.rs +++ b/src/omikron/omikron_connection.rs @@ -348,11 +348,7 @@ impl OmikronConnection { } if cv.is_type(CommunicationType::identification_response) { if let Some(accepted) = cv.get_data(DataTypes::accepted) { - if accepted.as_str().unwrap_or("0") != "0" { - log_message(format!("Omikron connected: {}", accepted.to_string())); - } else { - log_message("omikron_connection_failed"); - } + log_message(format!("Omikron connected: {}", accepted.to_string())); } return; } diff --git a/src/users/mod.rs b/src/users/mod.rs index 7e16bb5..aef5a02 100644 --- a/src/users/mod.rs +++ b/src/users/mod.rs @@ -1,4 +1,4 @@ -pub mod contact; -pub mod user_community_util; -pub mod user_manager; -pub mod user_profile; +pub mod contact; +pub mod user_community_util; +pub mod user_manager; +pub mod user_profile; diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs index 17296ac..9d9a078 100644 --- a/src/util/crypto_helper.rs +++ b/src/util/crypto_helper.rs @@ -1,129 +1,129 @@ -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 opertions -#[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( - 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(); - 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( - 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(); - 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() -} +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 opertions +#[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( + 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(); + 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( + 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(); + 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..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(), + }) + } +}