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

View file

@ -93,15 +93,15 @@ impl ConsentUiState {
.as_secs();
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/\
\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/\
\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/\
\nToS={}\
\nThis file reflects the current consent state used by the application.\
\nIt may be regenerated or overwritten by the application.\
\nThis file was last edited by Tensamin at:\
\nUNIX-SECOND={}",
\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/\
\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/\
\nToS={}\
\nThis file reflects the current consent state used by the application.\
\nIt may be regenerated or overwritten by the application.\
\nThis file was last edited by Tensamin at:\
\nUNIX-SECOND={}",
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::users::contact::Contact;
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::crypto_util::{DataFormat, SecurePayload};
use crate::util::file_util::{get_children, load_file, save_file};
@ -13,14 +14,15 @@ use crate::{
util::{config_util::CONFIG, crypto_helper},
};
use dashmap::DashMap;
use futures::Stream;
use futures::stream::{SplitSink, SplitStream};
use futures::{FutureExt, Stream};
use futures_util::sink::Sink;
use futures_util::{SinkExt, StreamExt};
use hyper::upgrade::Upgraded;
use hyper_util::rt::TokioIo;
use json::JsonValue;
use json::number::Number;
use pkcs8::DecodePrivateKey;
use std::collections::HashMap;
use std::sync::{Arc, LazyLock};
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 tungstenite::Utf8Bytes;
use uuid::Uuid;
use warp::reply::Json;
pub static OMIKRON_CONNECTION: LazyLock<Arc<RwLock<Option<Arc<OmikronConnection>>>>> =
LazyLock::new(|| Arc::new(RwLock::new(None)));
@ -148,13 +151,10 @@ impl OmikronConnection {
// Login flow
if self.connect_internal().await {
self.send_message(
CommunicationValue::new(CommunicationType::identification)
.add_data(
DataTypes::iota_id,
JsonValue::Number(json::number::Number::from(iota_id)),
)
.to_json()
.to_string(),
&CommunicationValue::new(CommunicationType::identification).add_data(
DataTypes::iota_id,
JsonValue::Number(json::number::Number::from(iota_id)),
),
)
.await;
}
@ -207,8 +207,13 @@ impl OmikronConnection {
true
}
pub async fn send_message(&self, msg: String) {
Self::send_message_static(&self.writer, Arc::clone(&self.is_connected), msg).await;
pub async fn send_message(&self, cv: &CommunicationValue) {
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) {
@ -228,7 +233,6 @@ impl OmikronConnection {
let is_connected_out = self.is_connected.clone();
let sel_out = self.clone();
let variant = self.variant.clone();
let sel_arc_out = self.clone();
{
ACTIVE_TASKS.lock().unwrap().push("Listener".to_string());
@ -238,14 +242,12 @@ impl OmikronConnection {
if *SHUTDOWN.read().await {
break;
}
Self::handle_message(
sel_out.clone().handle_message(
msg,
waiting_out.clone(),
writer_out.clone(),
is_connected_out.clone(),
sel_out.clone(),
variant.clone(),
sel_arc_out.clone(),
);
}
*is_connected_out.lock().await = false;
@ -259,6 +261,7 @@ impl OmikronConnection {
}
}
pub fn handle_message(
self: Arc<Self>,
msg: Result<Message, tungstenite::Error>,
waiting: Arc<DashMap<Uuid, Box<dyn Fn(CommunicationValue) + Send + Sync + 'static>>>,
writer: Arc<
@ -267,9 +270,7 @@ impl OmikronConnection {
>,
>,
is_connected: Arc<Mutex<bool>>,
sel: Arc<OmikronConnection>,
variant: Arc<RwLock<ConnectionVariant>>,
sel_arc: Arc<OmikronConnection>,
) {
tokio::spawn(async move {
match msg {
@ -281,7 +282,7 @@ impl OmikronConnection {
Ok(Message::Text(text)) => {
let cv = CommunicationValue::from_json(&text);
if cv.is_type(CommunicationType::pong) {
sel.handle_pong(&cv, true).await;
self.handle_pong(&cv, true).await;
return;
}
if cv.is_type(CommunicationType::challenge) {
@ -324,7 +325,7 @@ impl OmikronConnection {
JsonValue::String(decrypted.export(DataFormat::Base64)),
);
sel_arc.send_message(response.to_json().to_string()).await;
self.send_message(&response).await;
} else {
log_message("Failed to decrypt challenge");
}
@ -349,13 +350,11 @@ impl OmikronConnection {
.add_data(
DataTypes::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 {
sel_arc_clone.send_message(login_message).await;
self_clone.send_message(&login_message).await;
});
} else {
log_message("Iota registration failed.");
@ -379,16 +378,13 @@ impl OmikronConnection {
.as_i64()
.unwrap_or(0);
if user_id == 0 {
sel_arc
.send_message(
CommunicationValue::new(
CommunicationType::error_invalid_user_id,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
self.send_message(
&CommunicationValue::new(
CommunicationType::error_invalid_user_id,
)
.await;
.with_id(cv.get_id()),
)
.await;
return;
}
@ -403,44 +399,37 @@ impl OmikronConnection {
if !is_valid {
log_message("Invalid private key");
sel_arc
.send_message(
CommunicationValue::new(
CommunicationType::error_invalid_private_key,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
self.send_message(
&CommunicationValue::new(
CommunicationType::error_invalid_private_key,
)
.await;
.with_id(cv.get_id()),
)
.await;
return;
}
} else {
log_message("Missing private key");
sel_arc
.send_message(
CommunicationValue::new(
CommunicationType::error_invalid_private_key,
)
.with_id(cv.get_id())
.to_json()
.to_string(),
self.send_message(
&CommunicationValue::new(
CommunicationType::error_invalid_private_key,
)
.await;
.with_id(cv.get_id()),
)
.await;
return;
}
// Set identification data
sel_arc.set_user_id(user_id).await;
sel_arc
.set_variant(ConnectionVariant::ClientAuthenticated)
self.set_user_id(user_id).await;
self.set_variant(ConnectionVariant::ClientAuthenticated)
.await;
let response =
CommunicationValue::new(CommunicationType::identification_response)
.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);
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) {
let sender_id = &cv.get_sender();
let receiver_id = &cv.get_receiver();
@ -485,12 +493,50 @@ impl OmikronConnection {
DataTypes::sender_id,
JsonValue::Number(Number::from(cv.get_sender())),
);
Self::send_message_static(
&writer.clone(),
is_connected,
user_forward.to_json().to_string(),
)
.await;
let user_resp = self
.clone()
.await_response(&user_forward, Some(Duration::from_secs(10)))
.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;
}
@ -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));
match tokio::time::timeout(timeout, rx.recv()).await {
Ok(Some(response_cv)) => Ok(response_cv),
Ok(None) => Err("Failed to receive response, channel was closed.".to_string()),
Ok(_) => Err("Failed to receive response, channel was closed.".to_string()),
Err(_) => {
self.waiting.remove(&msg_id);
Err(format!(

View file

@ -20,11 +20,9 @@ impl OmikronConnection {
.add_data_num(
DataTypes::last_ping,
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

View file

@ -5,20 +5,40 @@ use std::path::Path;
use crate::gui::log_panel::log_message;
#[derive(PartialEq, Debug, Clone)]
pub enum MessageState {
Read,
Received,
Send,
Sending,
Error,
}
impl MessageState {
fn as_str(&self) -> &'static str {
pub fn as_str(&self) -> &'static str {
match self {
MessageState::Read => "READ",
MessageState::Received => "RECEIVED",
MessageState::Send => "SEND",
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());
}
pub fn change_message_state(
timestamp: i64,
storage_owner: i64,
external_user: i64,
timestamp: i64,
new_state: MessageState,
) -> std::io::Result<()> {
let user_dir = format!("users/{}/chats/{}", storage_owner, external_user);
@ -114,7 +134,13 @@ pub fn change_message_state(
let mut modified = false;
for i in 0..chunk.len() {
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;
break;
}

View file

@ -109,11 +109,6 @@ impl SecurePayload {
let peer_pub = public_key.into();
let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap();
println!(
"Encryption Shared Secret (Hex): {}",
hex::encode(shared_secret.as_bytes())
);
// 3. Key & Nonce Derivation (HKDF)
// We derive 32 bytes for the key and 12 bytes for a deterministic nonce.
let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
@ -168,12 +163,6 @@ impl SecurePayload {
let peer_pub = peer_public_key_bytes.into();
let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap();
// LOGGING: Shared Secret
println!(
"Decryption Shared Secret (Hex): {}",
hex::encode(shared_secret.as_bytes())
);
// 2. Key & Nonce Derivation (Must match encryption exactly)
let hkdf = Hkdf::<Sha256>::new(None, shared_secret.as_bytes());
let mut okm = [0u8; 44];