[Add] base of message states

This commit is contained in:
Alex Emmet 2026-02-01 16:29:09 +01:00
commit 87d4ffab19
6 changed files with 153 additions and 91 deletions

View file

@ -53,6 +53,7 @@ pub enum DataTypes {
signature, signature,
signed, signed,
message, message,
message_state,
last_ping, last_ping,
ping_iota, ping_iota,
ping_clients, ping_clients,
@ -112,6 +113,7 @@ impl DataTypes {
#[allow(non_camel_case_types, dead_code)] #[allow(non_camel_case_types, dead_code)]
pub enum CommunicationType { pub enum CommunicationType {
error, error,
error_anonymous,
error_internal, error_internal,
error_invalid_data, error_invalid_data,
error_invalid_user_id, error_invalid_user_id,
@ -134,6 +136,7 @@ pub enum CommunicationType {
settings_load, settings_load,
settings_list, settings_list,
message, message,
message_state,
message_send, message_send,
message_live, message_live,
message_other_iota, message_other_iota,

View file

@ -93,15 +93,15 @@ impl ConsentUiState {
.as_secs(); .as_secs();
format!( format!(
"\"EULA=true\" indicates that you read and accepted the End User Licence agreement. You can find our EULA at https://docs.tensamin.net/legal/eula/\ "\"EULA=true\" indicates that you read and accepted the End User Licence agreement. You can find our EULA at https://docs.tensamin.net/legal/eula/\
\nEULA={}\ \nEULA={}\
\n\"PrivacyPolicy=true\" indicates that you read and accepted the Privacy Policy. You can find our Privacy Policy at https://docs.tensamin.net/legal/privacy-policy/\ \n\"PrivacyPolicy=true\" indicates that you read and accepted the Privacy Policy. You can find our Privacy Policy at https://docs.tensamin.net/legal/privacy-policy/\
\nPrivacyPolicy={}\ \nPrivacyPolicy={}\
\n\"ToS=true\" indicates that you read and accepted the Terms of Service. You can find our Terms of Service at https://docs.tensamin.net/legal/terms-of-service/\ \n\"ToS=true\" indicates that you read and accepted the Terms of Service. You can find our Terms of Service at https://docs.tensamin.net/legal/terms-of-service/\
\nToS={}\ \nToS={}\
\nThis file reflects the current consent state used by the application.\ \nThis file reflects the current consent state used by the application.\
\nIt may be regenerated or overwritten by the application.\ \nIt may be regenerated or overwritten by the application.\
\nThis file was last edited by Tensamin at:\ \nThis file was last edited by Tensamin at:\
\nUNIX-SECOND={}", \nUNIX-SECOND={}",
self.eula, self.pp, self.tos, current_secs self.eula, self.pp, self.tos, current_secs
) )
} }

View file

@ -2,6 +2,7 @@ use crate::auth::local_auth;
use crate::gui::log_panel::{log_cv, log_message_format}; use crate::gui::log_panel::{log_cv, log_message_format};
use crate::users::contact::Contact; use crate::users::contact::Contact;
use crate::users::user_community_util::UserCommunityUtil; use crate::users::user_community_util::UserCommunityUtil;
use crate::util::chat_files::MessageState;
use crate::util::chats_util::{get_user, mod_user}; use crate::util::chats_util::{get_user, mod_user};
use crate::util::crypto_util::{DataFormat, SecurePayload}; use crate::util::crypto_util::{DataFormat, SecurePayload};
use crate::util::file_util::{get_children, load_file, save_file}; use crate::util::file_util::{get_children, load_file, save_file};
@ -13,14 +14,15 @@ use crate::{
util::{config_util::CONFIG, crypto_helper}, util::{config_util::CONFIG, crypto_helper},
}; };
use dashmap::DashMap; use dashmap::DashMap;
use futures::Stream;
use futures::stream::{SplitSink, SplitStream}; use futures::stream::{SplitSink, SplitStream};
use futures::{FutureExt, Stream};
use futures_util::sink::Sink; use futures_util::sink::Sink;
use futures_util::{SinkExt, StreamExt}; use futures_util::{SinkExt, StreamExt};
use hyper::upgrade::Upgraded; use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use json::JsonValue; use json::JsonValue;
use json::number::Number; use json::number::Number;
use pkcs8::DecodePrivateKey;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, LazyLock}; use std::sync::{Arc, LazyLock};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
@ -29,6 +31,7 @@ use tokio::time::{Duration, Instant, sleep};
use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; use tokio_tungstenite::{connect_async, tungstenite::protocol::Message};
use tungstenite::Utf8Bytes; use tungstenite::Utf8Bytes;
use uuid::Uuid; use uuid::Uuid;
use warp::reply::Json;
pub static OMIKRON_CONNECTION: LazyLock<Arc<RwLock<Option<Arc<OmikronConnection>>>>> = pub static OMIKRON_CONNECTION: LazyLock<Arc<RwLock<Option<Arc<OmikronConnection>>>>> =
LazyLock::new(|| Arc::new(RwLock::new(None))); LazyLock::new(|| Arc::new(RwLock::new(None)));
@ -148,13 +151,10 @@ impl OmikronConnection {
// Login flow // Login flow
if self.connect_internal().await { if self.connect_internal().await {
self.send_message( self.send_message(
CommunicationValue::new(CommunicationType::identification) &CommunicationValue::new(CommunicationType::identification).add_data(
.add_data( DataTypes::iota_id,
DataTypes::iota_id, JsonValue::Number(json::number::Number::from(iota_id)),
JsonValue::Number(json::number::Number::from(iota_id)), ),
)
.to_json()
.to_string(),
) )
.await; .await;
} }
@ -207,8 +207,13 @@ impl OmikronConnection {
true true
} }
pub async fn send_message(&self, msg: String) { pub async fn send_message(&self, cv: &CommunicationValue) {
Self::send_message_static(&self.writer, Arc::clone(&self.is_connected), msg).await; Self::send_message_static(
&self.writer,
Arc::clone(&self.is_connected),
cv.to_json().to_string(),
)
.await;
} }
pub async fn set_variant(self: &Arc<Self>, variant: ConnectionVariant) { pub async fn set_variant(self: &Arc<Self>, variant: ConnectionVariant) {
@ -228,7 +233,6 @@ impl OmikronConnection {
let is_connected_out = self.is_connected.clone(); let is_connected_out = self.is_connected.clone();
let sel_out = self.clone(); let sel_out = self.clone();
let variant = self.variant.clone(); let variant = self.variant.clone();
let sel_arc_out = self.clone();
{ {
ACTIVE_TASKS.lock().unwrap().push("Listener".to_string()); ACTIVE_TASKS.lock().unwrap().push("Listener".to_string());
@ -238,14 +242,12 @@ impl OmikronConnection {
if *SHUTDOWN.read().await { if *SHUTDOWN.read().await {
break; break;
} }
Self::handle_message( sel_out.clone().handle_message(
msg, msg,
waiting_out.clone(), waiting_out.clone(),
writer_out.clone(), writer_out.clone(),
is_connected_out.clone(), is_connected_out.clone(),
sel_out.clone(),
variant.clone(), variant.clone(),
sel_arc_out.clone(),
); );
} }
*is_connected_out.lock().await = false; *is_connected_out.lock().await = false;
@ -259,6 +261,7 @@ impl OmikronConnection {
} }
} }
pub fn handle_message( pub fn handle_message(
self: Arc<Self>,
msg: Result<Message, tungstenite::Error>, msg: Result<Message, tungstenite::Error>,
waiting: Arc<DashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync + 'static>>>, waiting: Arc<DashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync + 'static>>>,
writer: Arc< writer: Arc<
@ -267,9 +270,7 @@ impl OmikronConnection {
>, >,
>, >,
is_connected: Arc<Mutex<bool>>, is_connected: Arc<Mutex<bool>>,
sel: Arc<OmikronConnection>,
variant: Arc<RwLock<ConnectionVariant>>, variant: Arc<RwLock<ConnectionVariant>>,
sel_arc: Arc<OmikronConnection>,
) { ) {
tokio::spawn(async move { tokio::spawn(async move {
match msg { match msg {
@ -281,7 +282,7 @@ impl OmikronConnection {
Ok(Message::Text(text)) => { Ok(Message::Text(text)) => {
let cv = CommunicationValue::from_json(&text); let cv = CommunicationValue::from_json(&text);
if cv.is_type(CommunicationType::pong) { if cv.is_type(CommunicationType::pong) {
sel.handle_pong(&cv, true).await; self.handle_pong(&cv, true).await;
return; return;
} }
if cv.is_type(CommunicationType::challenge) { if cv.is_type(CommunicationType::challenge) {
@ -324,7 +325,7 @@ impl OmikronConnection {
JsonValue::String(decrypted.export(DataFormat::Base64)), JsonValue::String(decrypted.export(DataFormat::Base64)),
); );
sel_arc.send_message(response.to_json().to_string()).await; self.send_message(&response).await;
} else { } else {
log_message("Failed to decrypt challenge"); log_message("Failed to decrypt challenge");
} }
@ -349,13 +350,11 @@ impl OmikronConnection {
.add_data( .add_data(
DataTypes::iota_id, DataTypes::iota_id,
JsonValue::Number(json::number::Number::from(iota_id)), JsonValue::Number(json::number::Number::from(iota_id)),
) );
.to_json()
.to_string();
let sel_arc_clone = sel_arc.clone(); let self_clone = self.clone();
tokio::spawn(async move { tokio::spawn(async move {
sel_arc_clone.send_message(login_message).await; self_clone.send_message(&login_message).await;
}); });
} else { } else {
log_message("Iota registration failed."); log_message("Iota registration failed.");
@ -379,16 +378,13 @@ impl OmikronConnection {
.as_i64() .as_i64()
.unwrap_or(0); .unwrap_or(0);
if user_id == 0 { if user_id == 0 {
sel_arc self.send_message(
.send_message( &CommunicationValue::new(
CommunicationValue::new( CommunicationType::error_invalid_user_id,
CommunicationType::error_invalid_user_id,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
) )
.await; .with_id(cv.get_id()),
)
.await;
return; return;
} }
@ -403,44 +399,37 @@ impl OmikronConnection {
if !is_valid { if !is_valid {
log_message("Invalid private key"); log_message("Invalid private key");
sel_arc self.send_message(
.send_message( &CommunicationValue::new(
CommunicationValue::new( CommunicationType::error_invalid_private_key,
CommunicationType::error_invalid_private_key,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
) )
.await; .with_id(cv.get_id()),
)
.await;
return; return;
} }
} else { } else {
log_message("Missing private key"); log_message("Missing private key");
sel_arc self.send_message(
.send_message( &CommunicationValue::new(
CommunicationValue::new( CommunicationType::error_invalid_private_key,
CommunicationType::error_invalid_private_key,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
) )
.await; .with_id(cv.get_id()),
)
.await;
return; return;
} }
// Set identification data // Set identification data
sel_arc.set_user_id(user_id).await; self.set_user_id(user_id).await;
sel_arc self.set_variant(ConnectionVariant::ClientAuthenticated)
.set_variant(ConnectionVariant::ClientAuthenticated)
.await; .await;
let response = let response =
CommunicationValue::new(CommunicationType::identification_response) CommunicationValue::new(CommunicationType::identification_response)
.with_id(cv.get_id()); .with_id(cv.get_id());
sel_arc.send_message(response.to_json().to_string()).await; self.send_message(&response).await;
} }
} }
// ************************************************ // // ************************************************ //
@ -451,6 +440,25 @@ impl OmikronConnection {
y(cv); y(cv);
return; return;
} }
if cv.is_type(CommunicationType::message_state) {
let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver();
chat_files::change_message_state(
cv.get_data(DataTypes::send_time)
.unwrap_or(&JsonValue::new_object())
.as_i64()
.unwrap_or(0) as i64,
*receiver_id,
*sender_id,
MessageState::from_str(
cv.get_data(DataTypes::message_state)
.unwrap_or(&JsonValue::Null)
.as_str()
.unwrap_or(""),
),
);
}
if cv.is_type(CommunicationType::message_other_iota) { if cv.is_type(CommunicationType::message_other_iota) {
let sender_id = &cv.get_sender(); let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver(); let receiver_id = &cv.get_receiver();
@ -485,12 +493,50 @@ impl OmikronConnection {
DataTypes::sender_id, DataTypes::sender_id,
JsonValue::Number(Number::from(cv.get_sender())), JsonValue::Number(Number::from(cv.get_sender())),
); );
Self::send_message_static( let user_resp = self
&writer.clone(), .clone()
is_connected, .await_response(&user_forward, Some(Duration::from_secs(10)))
user_forward.to_json().to_string(), .await;
)
.await; if let Ok(user_resp) = user_resp {
let ms: MessageState = MessageState::from_str(
user_resp
.get_data(DataTypes::message_state)
.unwrap_or(&JsonValue::Null)
.as_str()
.unwrap_or(""),
)
.upgrade(MessageState::Send);
self.send_message(
&CommunicationValue::new(CommunicationType::message_state)
.with_id(cv.get_id())
.with_receiver(*sender_id)
.with_sender(*receiver_id)
.add_data(
DataTypes::send_time,
cv.get_data(DataTypes::send_time).unwrap().clone(),
)
.add_data(
DataTypes::message_state,
JsonValue::from(ms.as_str()),
),
);
} else {
self.send_message(
&CommunicationValue::new(CommunicationType::message_state)
.with_id(cv.get_id())
.with_receiver(*sender_id)
.with_sender(*receiver_id)
.add_data(
DataTypes::send_time,
cv.get_data(DataTypes::send_time).unwrap().clone(),
)
.add_data(
DataTypes::message_state,
JsonValue::from(MessageState::Send.as_str()),
),
);
}
return; return;
} }
@ -821,13 +867,13 @@ impl OmikronConnection {
}), }),
); );
self.send_message(cv.to_json().to_string()).await; self.send_message(&cv).await;
let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); let timeout = timeout_duration.unwrap_or(Duration::from_secs(10));
match tokio::time::timeout(timeout, rx.recv()).await { match tokio::time::timeout(timeout, rx.recv()).await {
Ok(Some(response_cv)) => Ok(response_cv), Ok(Some(response_cv)) => Ok(response_cv),
Ok(None) => Err("Failed to receive response, channel was closed.".to_string()), Ok(_) => Err("Failed to receive response, channel was closed.".to_string()),
Err(_) => { Err(_) => {
self.waiting.remove(&msg_id); self.waiting.remove(&msg_id);
Err(format!( Err(format!(

View file

@ -20,11 +20,9 @@ impl OmikronConnection {
.add_data_num( .add_data_num(
DataTypes::last_ping, DataTypes::last_ping,
Number::from(*self.last_ping.lock().await), Number::from(*self.last_ping.lock().await),
) );
.to_json()
.to_string();
self.send_message(ping_message).await; self.send_message(&ping_message).await;
} }
/// Handles incoming pong and calculates latency /// Handles incoming pong and calculates latency

View file

@ -5,20 +5,40 @@ use std::path::Path;
use crate::gui::log_panel::log_message; use crate::gui::log_panel::log_message;
#[derive(PartialEq, Debug, Clone)]
pub enum MessageState { pub enum MessageState {
Read, Read,
Received, Received,
Send,
Sending, Sending,
Error,
} }
impl MessageState { impl MessageState {
fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match self { match self {
MessageState::Read => "READ", MessageState::Read => "READ",
MessageState::Received => "RECEIVED", MessageState::Received => "RECEIVED",
MessageState::Send => "SEND",
MessageState::Sending => "SENDING", MessageState::Sending => "SENDING",
MessageState::Error => "ERROR", }
}
pub fn from_str(str: &str) -> Self {
match str.to_uppercase().as_str() {
"READ" => MessageState::Read,
"RECEIVED" => MessageState::Received,
"SEND" => MessageState::Send,
_ => MessageState::Sending,
}
}
pub fn upgrade(self, other: Self) -> Self {
if other == Self::Read || self == Self::Read {
Self::Read
} else if other == Self::Received || self == Self::Received {
Self::Received
} else if other == Self::Send || self == Self::Send {
Self::Send
} else {
Self::Sending
} }
} }
} }
@ -86,9 +106,9 @@ pub fn add_message(
save_file(&user_dir, &file_name, &message_chunk.dump()); save_file(&user_dir, &file_name, &message_chunk.dump());
} }
pub fn change_message_state( pub fn change_message_state(
timestamp: i64,
storage_owner: i64, storage_owner: i64,
external_user: i64, external_user: i64,
timestamp: i64,
new_state: MessageState, new_state: MessageState,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user); let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
@ -114,7 +134,13 @@ pub fn change_message_state(
let mut modified = false; let mut modified = false;
for i in 0..chunk.len() { for i in 0..chunk.len() {
if chunk[i]["message_time"].as_i64() == Some(timestamp) { if chunk[i]["message_time"].as_i64() == Some(timestamp) {
chunk[i]["message_state"] = JsonValue::from(new_state.as_str()); chunk[i]["message_state"] = JsonValue::from(
MessageState::from_str(
chunk[i]["message_state"].as_str().unwrap_or("SENDING"),
)
.upgrade(new_state.clone())
.as_str(),
);
modified = true; modified = true;
break; break;
} }

View file

@ -109,11 +109,6 @@ impl SecurePayload {
let peer_pub = public_key.into(); let peer_pub = public_key.into();
let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); 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) // 3. Key & Nonce Derivation (HKDF)
// We derive 32 bytes for the key and 12 bytes for a deterministic nonce. // We derive 32 bytes for the key and 12 bytes for a deterministic nonce.
let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes()); let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
@ -168,12 +163,6 @@ impl SecurePayload {
let peer_pub = peer_public_key_bytes.into(); let peer_pub = peer_public_key_bytes.into();
let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).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) // 2. Key & Nonce Derivation (Must match encryption exactly)
let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes()); let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut okm = [0u8; 44]; let mut okm = [0u8; 44];