Can now connect to Omikron :C

Some util
This commit is contained in:
Alex Emmet 2025-09-05 23:00:24 +02:00
commit 9bd22d4e3e
16 changed files with 1508 additions and 199 deletions

View file

@ -1,9 +1,10 @@
use std::time::Duration;
use std::collections::HashMap;
use uuid::Uuid;
use reqwest::blocking::{Client, Response};
use uuid::{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};
#[derive(Debug, Clone)]
pub struct AuthUser {
@ -29,8 +30,8 @@ impl AuthConnector {
.unwrap()
}
pub fn unregister_user(auth_server: &str, user_id: Uuid, reset_token: &str) -> bool {
let url = format!("https://{}/api/delete/{}", auth_server, user_id);
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 client = Self::client();
let mut payload = JsonValue::new_object();
@ -38,64 +39,61 @@ impl AuthConnector {
let res = client.post(&url)
.header(CONTENT_TYPE, "application/json")
.body(payload.dump())
.send();
match res {
Ok(resp) => Self::handle_response(resp),
Err(_) => false,
}
.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 fn get_uuid(auth_server: &str, username: &str) -> Option<Uuid> {
let url = format!("https://{}/api/get/uuid/{}", auth_server, username);
pub async fn get_uuid(username: &str) -> Option<Uuid> {
let url = format!("https://auth.tensamin.methanium.net/api/get/uuid/{}/", username);
let client = Self::client();
let res = client.get(&url).send().ok()?;
let json = res.text().ok()?;
let cv = CommunicationValue::from_string(&json);
if !cv.is_success() {
let res = client.get(&url).send().await.ok()?;
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
if !cv.is_type(CommunicationType::Success) {
return None;
}
cv.get_user_id()
Uuid::parse_str(cv.get_data(DataTypes::UserId).unwrap()).ok()
}
pub fn get_user(auth_server: &str, user_id: Uuid) -> Option<AuthUser> {
let url = format!("https://{}/api/get/{}", auth_server, user_id);
pub async fn get_user(user_id: Uuid) -> Option<AuthUser> {
let url = format!("https://auth.tensamin.methanium.net/api/get/{}/", user_id);
let client = Self::client();
let res = client.get(&url).send().ok()?;
let json = res.text().ok()?;
let cv = CommunicationValue::from_string(&json);
if !cv.is_success() {
let res = client.get(&url).send().await.ok()?;
let json = res.text().await.ok()?;
let mut cv = CommunicationValue::from_json(&json);
if cv.comm_type != CommunicationType::Success {
return None;
}
Some(AuthUser {
created_at: cv.get_number("created_at")? as i64,
username: cv.get_string("username")?,
display: cv.get_string("display")?,
avatar: cv.get_string("avatar")?,
about: cv.get_string("about")?,
status: cv.get_string("status")?,
public_key: cv.get_string("public_key")?,
sub_level: cv.get_number("sub_level")? as i32,
sub_end: cv.get_number("sub_end")? as i32,
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),
})
}
pub fn get_register(auth_server: &str) -> Option<Uuid> {
let url = format!("https://{}/api/register/init/", auth_server);
pub async fn get_register() -> Option<Uuid> {
let url = "https://auth.tensamin.methanium.net/api/register/init/".to_string();
let client = Self::client();
let res = client.get(&url).send().ok()?;
let json = res.text().ok()?;
let res = client.get(&url).send().await.ok()?;
let json = res.text().await.ok()?;
let cv = CommunicationValue::from_string(&json);
cv.get_user_id()
let mut cv = CommunicationValue::from_json(&json);
Uuid::parse_str(cv.get_data(DataTypes::UserId).unwrap()).ok()
}
pub fn complete_register(auth_server: &str, user_profile: &UserProfile, iota_id: &str) -> bool {
let url = format!("https://{}/api/register/complete/", auth_server);
pub async fn complete_register(user_profile: &UserProfile, iota_id: &str) -> bool {
let url = "https://auth.tensamin.methanium.net/api/register/complete/";
let client = Self::client();
let mut payload = JsonValue::new_object();
@ -106,19 +104,24 @@ impl AuthConnector {
payload["iota_id"] = iota_id.into();
payload["reset_token"] = user_profile.reset_token.clone().into();
let res = client.post(&url)
let res = client
.post(url)
.header(CONTENT_TYPE, "application/json")
.body(payload.dump())
.send();
.send()
.await;
match res {
Ok(resp) => Self::handle_response(resp),
Ok(resp) => Self::handle_response(resp).await,
Err(_) => false,
}
}
pub fn migrate_user(auth_server: &str, user_profile: &mut UserProfile, iota_id: &str) -> bool {
let url = format!("https://{}/api/change/iota-id/{}", auth_server, user_profile.user_id);
pub async fn migrate_user(user_profile: &mut UserProfile, iota_id: &str) -> bool {
let url = format!(
"https://auth.tensamin.methanium.net/api/change/iota-id/{}",
user_profile.user_id
);
let client = Self::client();
let mut payload = JsonValue::new_object();
@ -126,48 +129,28 @@ impl AuthConnector {
payload["reset_token"] = user_profile.reset_token.clone().into();
payload["new_token"] = user_profile.randomize_reset_token().into();
let res = client.post(&url)
let res = client
.post(url)
.header(CONTENT_TYPE, "application/json")
.body(payload.dump())
.send();
.send()
.await;
match res {
Ok(resp) => Self::handle_response(resp),
Ok(resp) => Self::handle_response(resp).await,
Err(_) => false,
}
}
fn handle_response(resp: Response) -> bool {
if let Ok(text) = resp.text() {
let cv = CommunicationValue::from_string(&text);
return cv.is_success();
async fn handle_response(resp: Response) -> bool {
match resp.text().await {
Ok(text) => {
let cv = CommunicationValue::from_json(&text);
cv.comm_type == CommunicationType::Success
}
Err(_) => false,
}
false
}
}
// --- Stubs for other modules ---
pub struct UserProfile {
pub user_id: Uuid,
pub public_key: String,
pub private_key_hash: String,
pub username: String,
pub reset_token: String,
}
impl UserProfile {
pub fn randomize_reset_token(&mut self) -> String {
// stub: generate new token
let new_tok = format!("{}-new", self.reset_token);
self.reset_token = new_tok.clone();
new_tok
}
}
pub struct CommunicationValue;
impl CommunicationValue {
pub fn from_string(s: &str) -> Self { Self }
pub fn is_success(&self) -> bool { true }
pub fn get_user_id(&self) -> Option<Uuid> { Some(Uuid::new_v4()) }
pub fn get_string(&self, _k: &str) -> Option<String> { Some("demo".to_string()) }
pub fn get_number(&self, _k: &str) -> Option<i64> { Some(123) }
}
}

View file

@ -1,8 +1,12 @@
use json::{object, JsonValue};
use std::any::Any;
use json::{object, parse, stringify, JsonValue};
use std::collections::HashMap;
use std::str::FromStr;
use std::time::{SystemTime, UNIX_EPOCH};
use axum::Json;
use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[derive(Eq, Hash, PartialEq, Debug)]
pub enum DataTypes {
ErrorType,
ChatPartnerId,
@ -71,7 +75,83 @@ pub enum DataTypes {
Communities,
}
#[derive(Debug, Clone, PartialEq, Eq)]
impl DataTypes {
pub fn parse(p0: String) -> DataTypes {
// normalize: lowercase + remove underscores
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,
"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,
"sharedsecretsign" => DataTypes::SharedSecretSign,
"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,
"communityaddress" => DataTypes::CommunityAddress,
"challenge" => DataTypes::Challenge,
"communitytitle" => DataTypes::CommunityTitle,
"communities" => DataTypes::Communities,
_ => DataTypes::ErrorType, // fallback if unknown
}
}
}
#[derive(PartialEq, Debug)]
pub enum CommunicationType {
Error,
Success,
@ -117,7 +197,59 @@ Error,
Function,
Update,
}
#[derive(Debug, Clone, PartialEq, Eq)]
impl CommunicationType {
pub fn parse(p0: String) -> CommunicationType {
let normalized = p0.to_lowercase().replace('_', "");
match normalized.as_str() {
"error" => CommunicationType::Error,
"success" => CommunicationType::Success,
"message" => CommunicationType::Message,
"messagelive" => CommunicationType::MessageLive,
"messageotheriota" => CommunicationType::MessageOtherIota,
"messagechunk" => CommunicationType::MessageChunk,
"messageget" => CommunicationType::MessageGet,
"changeconfirm" => CommunicationType::ChangeConfirm,
"confirmreceive" => CommunicationType::ConfirmReceive,
"confirmread" => CommunicationType::ConfirmRead,
"getchats" => CommunicationType::GetChats,
"getstates" => CommunicationType::GetStates,
"addcommunity" => CommunicationType::AddCommunity,
"removecommunity" => CommunicationType::RemoveCommunity,
"getcommunities" => CommunicationType::GetCommunities,
"challenge" => CommunicationType::Challenge,
"challengeresponse" => CommunicationType::ChallengeResponse,
"register" => CommunicationType::Register,
"registerresponse" => CommunicationType::RegisterResponse,
"identification" => CommunicationType::Identification,
"identificationresponse" => CommunicationType::IdentificationResponse,
"ping" => CommunicationType::Ping,
"pong" => CommunicationType::Pong,
"addchat" => CommunicationType::AddChat,
"sendchat" => CommunicationType::SendChat,
"iotaconnected" => CommunicationType::IotaConnected,
"iotaclosed" => CommunicationType::IotaClosed,
"clientchanged" => CommunicationType::ClientChanged,
"clientconnected" => CommunicationType::ClientConnected,
"clientclosed" => CommunicationType::ClientClosed,
"publickey" => CommunicationType::PublicKey,
"privatekey" => CommunicationType::PrivateKey,
"webrtcsdp" => CommunicationType::WebrtcSdp,
"webrtcice" => CommunicationType::WebrtcIce,
"startstream" => CommunicationType::StartStream,
"endstream" => CommunicationType::EndStream,
"watchstream" => CommunicationType::WatchStream,
"getcall" => CommunicationType::GetCall,
"newcall" => CommunicationType::NewCall,
"callinvite" => CommunicationType::CallInvite,
"endcall" => CommunicationType::EndCall,
"function" => CommunicationType::Function,
"update" => CommunicationType::Update,
_ => CommunicationType::Error, // fallback
}
}
}
#[derive(Debug, Clone)]
pub enum LogLevel {
Important = 2,
Normal = 1,
@ -140,22 +272,21 @@ impl LogValue {
}
}
pub fn to_json(&self) -> JsonValue {
pub fn to_json(self) -> JsonValue {
object! {
message: self.message.clone(),
log_level: self.log_level.clone() as i32
log_level: self.log_level as i32
}
}
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct CommunicationValue {
pub id: Uuid,
pub comm_type: CommunicationType,
pub log_value: Option<LogValue>,
pub sender: Option<Uuid>,
pub receiver: Option<Uuid>,
pub data: HashMap<DataTypes, JsonValue>,
pub data: HashMap<DataTypes, String>,
}
impl CommunicationValue {
@ -169,31 +300,49 @@ impl CommunicationValue {
data: HashMap::new(),
}
}
pub fn with_id(mut self, p0: Uuid) -> Self{
self.id = p0;
self
}
pub fn get_id(&self) -> Uuid{
self.id.clone()
}
pub fn with_log(mut self, log: LogValue) -> Self {
self.log_value = Some(log);
self
}
pub fn get_log(&self) -> &Option<LogValue> {
&self.log_value
}
pub fn with_sender(mut self, sender: Uuid) -> Self {
self.sender = Some(sender);
self
}
pub fn get_sender(&self) -> Option<Uuid> {
self.sender.clone()
}
pub fn with_receiver(mut self, receiver: Uuid) -> Self {
self.receiver = Some(receiver);
self
}
pub fn add_data(mut self, key: DataTypes, value: JsonValue) -> Self {
pub fn get_receiver(&self) -> Option<Uuid> {
self.receiver.clone()
}
pub fn add_data(mut self, key: DataTypes, value: String) -> Self {
self.data.insert(key, value);
self
}
pub fn get_data(&mut self, key: DataTypes) -> Option<&String> {
self.data.get(&key)
}
pub(crate) fn is_type(&self, p0: CommunicationType) -> bool {
self.comm_type == p0
}
pub fn to_json(&self) -> JsonValue {
let mut jdata = object!{};
for (k, v) in &self.data {
jdata[&format!("{:?}", k)] = v.clone();
jdata[&format!("{:?}", k)] = JsonValue::from(v.clone());
}
object! {
@ -201,23 +350,15 @@ impl CommunicationValue {
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(),
log: self.log_value.as_ref().map(|l| l.to_json()).unwrap_or(JsonValue::Null),
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) -> Result<Self, String> {
let parsed = json::parse(json_str).map_err(|e| e.to_string())?;
pub fn from_json(json_str: &str) -> Self {
let parsed = parse(json_str).unwrap();
let message_id = parsed["id"].as_str()
.and_then(|s| Uuid::parse_str(s).ok())
.unwrap_or_else(|| Uuid::new_v4());
let comm_type = match parsed["type"].as_str() {
Some("Message") => CommunicationType::Message,
Some("Success") => CommunicationType::Success,
_ => CommunicationType::Error,
};
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());
@ -230,14 +371,52 @@ impl CommunicationValue {
} else {
None
};
Ok(Self {
id: message_id,
let uuid = Uuid::parse_str(parsed["id"].as_str().unwrap_or("")).unwrap_or(Uuid::new_v4());
let mut data = HashMap::new();
if parsed["data"].is_object() {
for (k, v) in parsed["data"].entries() {
if let Some(val) = v.as_str() {
data.insert(DataTypes::parse(k.to_string()), val.to_string());
}
}
}
Self {
id: uuid,
comm_type,
log_value,
sender,
receiver,
data: HashMap::new(),
})
data,
}
}
pub fn ack_message(message_id: Uuid, sender: Option<Uuid>) -> CommunicationValue {
let mut cv = CommunicationValue::new(CommunicationType::Message)
.with_id(message_id);
if let Some(s) = sender {
cv = cv.add_data(DataTypes::SenderId, 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()).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)
.with_id(original.get_id())
.with_receiver(receiver.unwrap())
.add_data(DataTypes::SendTime, now_ms.to_string())
.add_data(DataTypes::MessageContent, 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, sender.to_string());
}
cv
}
}

View file

@ -1,32 +1,45 @@
use std::time::Duration;
use json::{self, JsonValue};
use tokio::time::sleep;
use std::process::{Command, ExitStatus};
use json::{
self
};
use uuid::Uuid;
mod data;
mod omikron;
mod util;
mod users;
mod auth;
use crate::omikron::omikronConnection::{OmikronConnection};
use crate::data::communication::{CommunicationValue, LogLevel, LogValue, CommunicationType, DataTypes};
use crate::auth::auth_connector::AuthConnector;
use crate::omikron::omikron_connection::{OmikronConnection};
use crate::data::communication::{CommunicationValue, CommunicationType, DataTypes};
fn main() {
let rt = tokio::runtime::Runtime::new().unwrap();
#[tokio::main]
async fn main() {
let alois = AuthConnector::get_uuid("aloisianer").await;
if alois.is_none() {
println!("No UUID");
return
}
println!("{}", alois.unwrap());
let omikron = OmikronConnection::new();
rt.block_on(async {
omikron.connect().await;
omikron.connect().await;
omikron.send_message(
CommunicationValue::new(
CommunicationType::Identification
)
.add_data(DataTypes::UserIds, json::JsonValue::String(Uuid::new_v4().to_string()))
.add_data(DataTypes::IotaId, json::JsonValue::String(Uuid::new_v4().to_string()))
.to_json()
.to_string()
.as_mut()
).await;
omikron.close().await;
});
OmikronConnection::send_message_static(
&omikron.writer,
CommunicationValue::new(
CommunicationType::Identification
)
.add_data(DataTypes::UserIds, Uuid::new_v4().to_string())
.add_data(DataTypes::IotaId, Uuid::new_v4().to_string())
.to_json()
.to_string()
.as_mut().parse().unwrap()
).await;
let mut child = Command::new("sleep").arg("2").spawn().unwrap();
let _result = child.wait().unwrap();
println!("reached end of main");
}

View file

@ -1 +1 @@
pub mod omikronConnection;
pub mod omikron_connection;

View file

@ -1,28 +1,29 @@
use futures_util::{SinkExt, StreamExt};
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tokio::sync::Mutex;
use tokio::net::TcpStream;
use tokio::time::{sleep, Duration};
use tokio_tungstenite::{
connect_async,
tungstenite::protocol::Message,
tungstenite::protocol::{Message},
MaybeTlsStream,
WebSocketStream,
};
use uuid::Uuid;
use json::JsonValue;
use crate::{
data::communication::CommunicationValue,
data::communication::CommunicationType,
data::communication::DataTypes
};
use uuid::{Uuid};
use crate::data::communication::{CommunicationType, CommunicationValue, DataTypes};
use crate::users::contact::Contact;
use crate::users::user_community_util::UserCommunityUtil;
use crate::util::chat_files::ChatFiles;
use crate::util::chats_util::ChatsUtil;
#[derive(Clone)]
pub struct OmikronConnection {
writer: Arc<Mutex<Option<futures_util::stream::SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>>,
waiting: Arc<Mutex<HashMap<Uuid, Box<dyn Fn(String) + Send>>>>,
pub(crate) writer: Arc<Mutex<Option<futures_util::stream::SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>>,
waiting: Arc<Mutex<HashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send>>>>, // waiting for responses
pingpong: Arc<Mutex<Option<tokio::task::JoinHandle<()>>>>, // ping-pong handler
}
impl OmikronConnection {
@ -30,79 +31,187 @@ impl OmikronConnection {
Self {
writer: Arc::new(Mutex::new(None)),
waiting: Arc::new(Mutex::new(HashMap::new())),
pingpong: Arc::new(Mutex::new(None)),
}
}
/// Connect loop with retry
pub async fn connect(&self) {
loop {
match connect_async("wss://tensamin.methanium.net/ws/iota/").await {
Ok((ws_stream, _)) => {
println!("[Omikron] Connected to server");
// Split into writer + reader
let (write_half, read_half) = ws_stream.split();
*self.writer.lock().await = Some(write_half);
// Spawn listener with read_half
self.spawn_listener(read_half);
break;
}
Err(e) => {
println!(
"[Omikron] Connection failed: {}. Retrying in 2s...",
e
);
sleep(Duration::from_secs(2)).await;
}
}
}
}
/// Gracefully close connection
pub async fn close(&self) {
let mut writer = self.writer.lock().await;
if let Some(mut ws) = writer.take() {
if let Some(handle) = self.pingpong.lock().await.take() {
handle.abort();
}
if let Some(mut ws) = self.writer.lock().await.take() {
let _ = ws.close().await;
}
println!("[Omikron] Connection closed");
}
/// Listener for all incoming messages
fn spawn_listener(
&self,
mut read_half: futures_util::stream::SplitStream<WebSocketStream<MaybeTlsStream<TcpStream>>>,
) {
let waiting = self.waiting.clone();
let writer = self.writer.clone();
tokio::spawn(async move {
while let Some(msg) = read_half.next().await {
match msg {
Ok(Message::Text(text)) => {
println!("[Omikron] Message received: {}", text);
if text.contains("\"type\":\"pong\"") {
println!("[Omikron] Pong received");
}
// Example: trigger callback if message_id is present
if let Some(id_pos) = text.find("\"message_id\":\"") {
let s = &text[id_pos + 14..];
if let Some(end) = s.find('"') {
let mid = &s[..end];
if let Ok(uuid) = Uuid::parse_str(mid) {
if let Some(callback) =
waiting.lock().await.remove(&uuid)
{
callback(text.to_string());
}
}
}
}
}
Ok(Message::Close(frame)) => {
Ok(Message::Close(Some(frame))) => {
println!("[Omikron] Closed: {:?}", frame);
break;
}
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
}
if waiting.lock().await.contains_key(&cv.get_id()) {
if let Some(callback) = waiting.lock().await.remove(&cv.get_id()) {
callback(cv);
}
continue;
}
// ************************************************ //
// Direct messages //
// ************************************************ //
if cv.is_type(CommunicationType::MessageOtherIota) {
let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver();
ChatFiles::add_message(
cv.get_data(DataTypes::SendTime).unwrap().parse::<i64>().unwrap_or(-1),
false,
receiver_id.unwrap(),
sender_id.unwrap(),
cv.get_data(DataTypes::MessageContent).unwrap().as_str(),
);
let response = CommunicationValue::new(CommunicationType::MessageLive)
.with_id(cv.get_id())
.with_receiver(cv.get_receiver().unwrap())
.add_data(DataTypes::SendTime, cv.get_data(DataTypes::SendTime).unwrap().to_string())
.add_data(DataTypes::Message, cv.get_data(DataTypes::MessageContent).unwrap().to_string())
.add_data(DataTypes::SenderId, cv.get_sender().unwrap().to_string());
Self::send_message_static(&writer, response.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::Message) {
let my_id = cv.get_sender();
ChatFiles::add_message(SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as i64,
true,
my_id.unwrap(),
Uuid::from_str(&*cv.get_data(DataTypes::ReceiverId).unwrap().to_string()).unwrap(),
&*cv.get_data(DataTypes::MessageContent).unwrap().to_string()
);
// ack
let ack = CommunicationValue::ack_message(cv.get_id(), my_id);
Self::send_message_static(&writer, ack.to_json().to_string()).await;
// forward
let forward = CommunicationValue::forward_to_other_iota(&mut cv);
Self::send_message_static(&writer, forward.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::MessageGet) {
let my_id = cv.get_sender().unwrap();
let partner_id = Uuid::from_str(&*cv.get_data(DataTypes::ChatPartnerId).unwrap().to_string()).unwrap();
let offset = cv.get_data(DataTypes::LoadedMessages).unwrap().to_string().parse::<i64>().unwrap();
let amount = cv.get_data(DataTypes::MessageAmount).unwrap().to_string().parse::<i64>().unwrap();
let messages = ChatFiles::get_messages(my_id, partner_id, offset, amount); // needs ChatFiles
let mut resp = CommunicationValue::new(CommunicationType::MessageChunk)
.with_id(cv.get_id())
.with_receiver(my_id);
if !messages.is_empty() {
resp = resp.add_data(DataTypes::MessageChunk, messages.to_string());
}
Self::send_message_static(&writer, resp.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::GetChats) {
let user_id = cv.get_sender();
let users = ChatsUtil::get_users(user_id.unwrap()); // needs ChatsUtil
let resp = CommunicationValue::new(CommunicationType::GetChats)
.with_id(cv.get_id())
.with_receiver(user_id.unwrap())
.add_data(DataTypes::UserIds, users.to_string());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::AddChat) {
let user_id = cv.get_sender();
let other_id = Uuid::from_str(&*cv.get_data(DataTypes::ReceiverId).unwrap().to_string()).unwrap();
let mut contact = ChatsUtil::get_user(user_id.unwrap(), 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);
ChatsUtil::mod_user(user_id.unwrap(), &contact);
let resp = CommunicationValue::new(CommunicationType::AddChat)
.with_id(cv.get_id())
.with_receiver(user_id.unwrap());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::AddCommunity) {
UserCommunityUtil::add_community( cv.get_sender().unwrap(),
cv.get_data(DataTypes::CommunityAddress).unwrap().to_string(),
cv.get_data(DataTypes::CommunityTitle).unwrap().to_string(),
cv.get_data(DataTypes::Position).unwrap().to_string()
);
let resp = CommunicationValue::new(CommunicationType::AddCommunity)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::GetCommunities) {
let resp = CommunicationValue::new(CommunicationType::GetCommunities)
.with_id(cv.get_id())
.with_receiver(cv.get_sender().unwrap())
.add_data(DataTypes::Communities, UserCommunityUtil::get_communities(cv.get_sender().unwrap()).to_string()); // needs UserCommunityUtil
Self::send_message_static(&writer, resp.to_json().to_string()).await;
continue;
}
if cv.is_type(CommunicationType::RemoveCommunity) {
UserCommunityUtil::remove_community(cv.get_sender().unwrap(), 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());
Self::send_message_static(&writer, resp.to_json().to_string()).await;
continue;
}
}
Err(e) => {
println!("[Omikron] Error: {}", e);
eprintln!("[Omikron] Error: {}", e);
break;
}
_ => {}
@ -111,18 +220,19 @@ impl OmikronConnection {
});
}
pub async fn send_message(&self, msg: &str) {
let mut guard = self.writer.lock().await;
pub async fn send_message_static(
writer: &Arc<Mutex<Option<futures_util::stream::SplitSink<WebSocketStream<MaybeTlsStream<TcpStream>>, Message>>>>,
msg: String,
) {
let mut guard = writer.lock().await;
if let Some(writer) = guard.as_mut() {
if let Err(e) = writer.send(Message::Text(msg.to_string())).await {
println!("[Omikron] Send failed: {}", e);
}
let _ = writer.send(Message::Text(msg)).await;
}
}
pub fn on_answer<F>(&self, message_id: Uuid, callback: F)
where
F: Fn(String) + Send + 'static,
F: Fn(CommunicationValue) + Send + 'static,
{
tokio::spawn({
let waiting = self.waiting.clone();
@ -135,4 +245,6 @@ impl OmikronConnection {
#[tokio::main]
async fn main() {
let conn = OmikronConnection::new();
conn.connect().await;
}

View file

@ -58,9 +58,12 @@ impl Contact {
about: None,
}
}
pub fn set_last_message_at(&mut self, p0: i64) {
self.last_message_at = Option::from(p0);
}
pub fn to_json(&self) -> JsonValue {
let mut obj = self.to_json();
let mut obj = JsonValue::new_object();
if let Some(id) = &self.user_id {
obj["userID"] = JsonValue::from(id.to_string());
}

View file

@ -1 +1,5 @@
pub mod contact;
pub mod contact;
pub mod user_manager;
pub mod user_profile_full;
pub mod user_profile;
pub mod user_community_util;

View file

@ -0,0 +1,64 @@
use json::{self, JsonValue};
use std::fs;
use std::path::Path;
use uuid::Uuid;
pub struct UserCommunityUtil;
impl UserCommunityUtil {
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);
let mut community = JsonValue::new_object();
community["title"] = JsonValue::String(title);
community["address"] = JsonValue::String(address);
community["position"] = JsonValue::String(position);
communities.push(community);
Self::save_array(&path, communities);
}
pub fn remove_community(storage_owner: Uuid, community_address: String) {
let path = format!("users/{}/communities.json", storage_owner);
let mut communities = Self::load_array(&path);
let mut new_array = JsonValue::new_array();
for entry in communities.members() {
if entry["address"].as_str() != Some(&community_address) {
new_array.push(entry.clone()).unwrap();
}
}
Self::save_array(&path, new_array);
}
pub fn get_communities(storage_owner: Uuid) -> JsonValue {
let path = format!("users/{}/communities.json", storage_owner);
Self::load_array(&path)
}
fn load_array(path: &str) -> JsonValue {
if !Path::new(path).exists() {
return JsonValue::new_object();
}
match fs::read_to_string(path) {
Ok(content) => json::parse(&content).unwrap_or_else(|_| JsonValue::new_object()),
Err(_) => JsonValue::new_object(),
}
}
fn save_array(path: &str, arr: JsonValue) {
if let Some(parent) = Path::new(path).parent() {
let _ = fs::create_dir_all(parent);
}
let _ = fs::write(path, arr.pretty(3));
}
}

View file

@ -0,0 +1,106 @@
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::users::user_profile::UserProfile;
use crate::users::user_profile_full::UserProfileFull;
pub struct UserManager;
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();
// 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 bytes = [0u8; 192];
OsRng.fill(bytes.as_mut());
let reset_token = general_purpose::STANDARD.encode(&bytes);
let up = UserProfile::new(
user_id,
username.to_string(),
None,
public_key,
private_key_hash,
reset_token,
);
let up_full = UserProfileFull { user_profile: up.clone(), private_key };
USERS.lock().unwrap().push(up);
Self::save_users().ok();
Some(up_full)
}
pub fn get_user(user_id: Uuid) -> Option<UserProfile> {
USERS.lock().unwrap().iter().cloned().find(|u| u.user_id == user_id)
}
pub fn get_users() -> Vec<UserProfile> {
USERS.lock().unwrap().clone()
}
pub fn add_user(up: UserProfile) {
let mut users = USERS.lock().unwrap();
users.retain(|u| u.user_id != up.user_id);
users.push(up);
*UNIQUE.lock().unwrap() = true;
}
pub fn remove_user(user_id: Uuid) {
let mut users = USERS.lock().unwrap();
users.retain(|u| u.user_id != user_id);
*UNIQUE.lock().unwrap() = true;
}
pub fn save_users() -> io::Result<()> {
*UNIQUE.lock().unwrap() = false;
let users = USERS.lock().unwrap();
let arr: Vec<JsonValue> = users.iter().map(|u| u.to_json()).collect();
let json_str = JsonValue::Array(arr).dump();
fs::write("users.json", json_str)?;
Ok(())
}
pub async fn load_users() -> io::Result<()> {
let path = Path::new("users.json");
if !path.exists() {
return Ok(());
}
let content = fs::read_to_string(path)?;
if content.trim().is_empty() {
return Ok(());
}
let parsed = json::parse(&content).map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
if let JsonValue::Array(arr) = parsed {
let mut users = USERS.lock().unwrap();
for j in arr.iter() {
if let Some(up) = UserProfile::from_json(j).await {
users.push(up);
}
}
}
if *UNIQUE.lock().unwrap() {
Self::save_users().ok();
}
Ok(())
}
pub fn set_unique(val: bool) {
*UNIQUE.lock().unwrap() = val;
}
}

View file

@ -0,0 +1,95 @@
use std::fs;
use std::sync::Mutex;
use std::collections::HashMap;
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, object, stringify};
use crate::users::user_manager::UserManager;
use crate::auth::auth_connector::AuthConnector;
// --- UserProfile ---
#[derive(Clone, Debug)]
pub struct UserProfile {
pub user_id: Uuid,
pub username: String,
pub public_key: String,
pub private_key_hash: String,
pub reset_token: String,
pub display_name: Option<String>,
}
impl UserProfile {
pub fn new(
user_id: Uuid,
username: String,
display_name: Option<String>,
public_key: String,
private_key_hash: String,
reset_token: String,
) -> Self {
Self {
user_id,
username,
display_name,
public_key,
private_key_hash,
reset_token,
}
}
pub fn to_json(&self) -> JsonValue {
let mut obj = object! {
"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()
};
if let Some(d) = &self.display_name {
obj["displayName"] = d.clone().into();
}
obj
}
pub async fn from_json(j: &JsonValue) -> Option<Self> {
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 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 AuthConnector::migrate_user(&mut up, stringify!("{}", Uuid::new_v4())).await {
println!("[INFO] Migration triggered for {}", up.username);
UserManager::set_unique(true);
}
}
Some(up)
}
pub fn randomize_reset_token(&mut self) -> String {
let mut bytes = [0u8; 192];
OsRng.fill(bytes.as_mut());
let new_token = general_purpose::STANDARD.encode(&bytes);
self.reset_token = new_token.clone();
UserManager::save_users().ok();
new_token
}
pub fn get_display_name(&self) -> String {
self.display_name.clone().unwrap_or_else(|| self.username.clone())
}
}

View file

@ -0,0 +1,8 @@
use crate::users::user_profile::UserProfile;
// --- UserProfileFull ---
#[derive(Clone, Debug)]
pub struct UserProfileFull {
pub user_profile: UserProfile,
pub private_key: String,
}

View file

@ -134,8 +134,8 @@ impl ChatFiles {
pub fn get_messages(
storage_owner: Uuid,
external_user: Uuid,
loaded_messages: usize,
amount: usize,
loaded_messages: i64,
amount: i64,
) -> JsonValue {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
let path = Path::new(&user_dir);

View file

@ -31,7 +31,7 @@ impl ChatsUtil {
file.write_all(content.as_bytes())
}
pub fn mod_user(storage_owner: Uuid, contact: Contact) -> std::io::Result<()> {
pub fn mod_user(storage_owner: Uuid, contact: &Contact){
let dir = format!("users/{}/contacts/", storage_owner);
let file_name = "contacts.json";
let s = Self::load_file(&dir, file_name);
@ -50,7 +50,7 @@ impl ChatsUtil {
}
contacts.push(contact.to_json()).unwrap();
Self::save_file(&dir, file_name, &contacts.dump())
Self::save_file(&dir, file_name, &contacts.dump());
}
pub fn get_user(storage_owner: Uuid, user_id: Uuid) -> Option<Contact> {

View file

@ -1,4 +1,4 @@
pub mod fileUtil;
pub mod chatFiles;
pub mod configUtil;
pub mod chatsUtil;
pub mod file_util;
pub mod chat_files;
pub mod config_util;
pub mod chats_util;