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
This commit is contained in:
Alex Emmet 2025-09-23 20:26:48 +00:00
commit 0d07ddf851
23 changed files with 887 additions and 266 deletions

View file

@ -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<bool> {
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<Uuid> {
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::<i64>().unwrap_or(-1),
created_at: cv
.get_data(DataTypes::CreatedAt)
.unwrap()
.to_string()
.parse::<i64>()
.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::<i32>().unwrap_or(-1),
sub_end: cv.get_data(DataTypes::SubEnd).unwrap().to_string().parse::<i32>().unwrap_or(-1),
sub_level: cv
.get_data(DataTypes::SubLevel)
.unwrap()
.to_string()
.parse::<i32>()
.unwrap_or(-1),
sub_end: cv
.get_data(DataTypes::SubEnd)
.unwrap()
.to_string()
.parse::<i32>()
.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,
}
}
}
}

View file

@ -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<LogValue>,
pub sender: Option<Uuid>,
pub receiver: Option<Uuid>,
pub sender: Uuid,
pub receiver: Uuid,
pub data: HashMap<DataTypes, JsonValue>,
}
@ -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<Uuid> {
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<Uuid> {
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<Uuid>) -> 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()))
}
}
}

0
src/gui/log_panel.rs Normal file
View file

View file

0
src/gui/user_panel.rs Normal file
View file

View file

@ -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(),
);
}

View file

@ -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<String, String>,
}
// Language packs need to have formatting
// Variables need to be provided
pub static LANGUAGE_PACK: Lazy<Mutex<LanguagePack>> =
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()
}
}

2
src/langu/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod language_creator;
pub mod language_manager;

View file

@ -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 {}
}

View file

@ -1 +1,2 @@
pub mod omikron_connection;
pub mod omikron_connection;
pub mod ping_pong_task;

View file

@ -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::<i64>()
.unwrap();
.unwrap_or(0);
let amount = cv
.get_data(DataTypes::MessageAmount)
.unwrap()
.unwrap_or(&JsonValue::Null)
.to_string()
.parse::<i64>()
.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;
}
}

View file

@ -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<OmikronConnection>, // assuming OmikronConnection is your connection type
pub message_send_times: Arc<Mutex<HashMap<Uuid, Instant>>>,
pub no_ping_in: Arc<Mutex<i32>>,
pub last_ping: Arc<Mutex<Option<u64>>>,
}
impl PingPongTask {
pub fn new(parent: Arc<OmikronConnection>) -> 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
}
}

View file

@ -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
}

View file

@ -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;
pub mod user_profile_full;

View file

@ -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));
}
}

View file

@ -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<Mutex<Vec<UserProfile>>> = Lazy::new(|| Mutex::new(Vec::new()
static UNIQUE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
impl UserManager {
pub fn create_user(username: &str) -> Option<UserProfileFull> {
// Stub: normally AuthConnector.getRegister() returns a UUID
let user_id = Uuid::new_v4();
pub async fn create_user(username: &str) -> Option<UserProfileFull> {
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<UserProfile> {
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<UserProfile> {

View file

@ -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<Self> {
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 isnt 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())
}
}
}

View file

@ -5,4 +5,4 @@ use crate::users::user_profile::UserProfile;
pub struct UserProfileFull {
pub user_profile: UserProfile,
pub private_key: String,
}
}

View file

@ -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<Contact> {
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]));
}

View file

@ -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<Mutex<ConfigUtil>> = 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;

View file

@ -1,4 +1,4 @@
pub mod file_util;
pub mod chat_files;
pub mod chats_util;
pub mod config_util;
pub mod chats_util;
pub mod file_util;