diff --git a/src/frontend/index.css b/src/frontend/index.css index 013aa92..c6e4c15 100644 --- a/src/frontend/index.css +++ b/src/frontend/index.css @@ -410,23 +410,42 @@ button:hover::after { overflow-y: auto; white-space: pre-wrap; } +#msg_log li { + display: flex; + align-items: end; + gap: 10px; + margin-bottom: 10px; + padding-right: 10px; +} +#msg_log li>div { + flex-grow: 1; +} +#msg_log li .timestamp { + opacity: .5; + font-family: "Liberation Sans", Arial, sans-serif; + font-size: .8em; +} #msg_log p { font-size: 1.1em; - word-break: break-word; margin: 0; } +#msg_log .avatar { + font-size: .8em; +} #msg_log li .header { display: flex; align-items: center; - margin-top: 15px; } #msg_log li .header p { color: var(--accent); font-weight: bold; + margin-left: .5em; } #msg_log li .content { margin-left: 3em; - margin-bottom: 10px; +} +#msg_log li .content p { + word-break: break-word; } #msg_log a { color: #238cf5; diff --git a/src/frontend/index.js b/src/frontend/index.js index 957c719..3e2d336 100644 --- a/src/frontend/index.js +++ b/src/frontend/index.js @@ -215,7 +215,7 @@ document.getElementById("attach_file").onchange = function(event) { formData.append("", files[i]); fetch("/send_file", {method: "POST", body: formData}).then(response => { if (response.ok) { - response.text().then(uuid => onFileSent(currentSessionId, uuid, files[i].name)); + response.text().then(uuid => onFileSent(currentSessionId, new Date.now(), uuid, files[i].name)); } else { console.log(response); } @@ -436,6 +436,16 @@ function getCookie(cname) { } return ""; } +function parseTimestamp(timestamp) { + return new Date(Number(timestamp) * 1000); +} +function toTwoNumbers(n) { + if (n < 10) { + return '0'+n; + } else { + return n; + } +} socket.onopen = function() { console.log("Connected"); @@ -467,10 +477,10 @@ socket.onmessage = function(msg) { onNewSession(args[1], args[2] === "true", args[3], args[4], msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+args[4].length+5)); break; case "new_message": - onNewMessage(args[1], args[2] === "true", msg.data.slice(args[0].length+args[1].length+args[2].length+3)); + onNewMessage(args[1], args[2] === "true", parseTimestamp(args[3]), msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+4)); break; case "file": - onFileReceived(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3)); + onFileReceived(args[1], parseTimestamp(args[2]), args[3], msg.data.slice(args[0].length+args[1].length+args[2].length+args[3].length+4)); break; case "files_transfer": onNewFilesTransfer(args[1], args[2], msg.data.slice(args[0].length+args[1].length+args[2].length+3)); @@ -600,8 +610,8 @@ function onMsgOrFileReceived(sessionId, outgoing, body) { } } } -function onNewMessage(sessionId, outgoing, msg) { - msgHistory.get(sessionId).push([outgoing, false, msg]); +function onNewMessage(sessionId, outgoing, timestamp, msg) { + msgHistory.get(sessionId).push([outgoing, timestamp, false, msg]); onMsgOrFileReceived(sessionId, outgoing, msg); } function onNewFilesTransfer(sessionId, index, filesInfo) { @@ -731,29 +741,32 @@ function onIncFilesTransfer(sessionId, chunkSize) { } function onMsgsLoad(sessionId, strMsgs) { let msgs = strMsgs.split(' '); - let n = 0; - while (n < msgs.length) { - let outgoing = msgs[n+1] === "true"; - switch (msgs[n]) { - case 'm': - let msg = b64DecodeUnicode(msgs[n+2]); - msgHistory.get(sessionId).unshift([outgoing, false, msg]); - n += 3; - break; - case 'f': - let uuid = msgs[n+2]; - let fileName = b64DecodeUnicode(msgs[n+3]); - msgHistory.get(sessionId).unshift([outgoing, true, [uuid, fileName]]); - n += 4; + if (msgs.length > 3) { + let n = 0; + while (n < msgs.length) { + let outgoing = msgs[n+1] === "true"; + let timestamp = parseTimestamp(msgs[n+2]); + switch (msgs[n]) { + case 'm': + let msg = b64DecodeUnicode(msgs[n+3]); + msgHistory.get(sessionId).unshift([outgoing, timestamp, false, msg]); + n += 4; + break; + case 'f': + let uuid = msgs[n+3]; + let fileName = b64DecodeUnicode(msgs[n+4]); + msgHistory.get(sessionId).unshift([outgoing, timestamp, true, [uuid, fileName]]); + n += 5; + } } - } - if (currentSessionId == sessionId) { - if (msg_log.scrollHeight - msg_log.scrollTop === msg_log.clientHeight) { - displayHistory(); - } else { - let backupHeight = msg_log.scrollHeight; - displayHistory(false); - msg_log.scrollTop = msg_log.scrollHeight-backupHeight; + if (currentSessionId == sessionId) { + if (msg_log.scrollHeight - msg_log.scrollTop === msg_log.clientHeight) { + displayHistory(); + } else { + let backupHeight = msg_log.scrollHeight; + displayHistory(false); + msg_log.scrollTop = msg_log.scrollHeight-backupHeight; + } } } } @@ -774,12 +787,12 @@ function onDisconnected(sessionId) { } displaySessions(); } -function onFileReceived(sessionId, uuid, file_name) { - msgHistory.get(sessionId).push([false, true, [uuid, file_name]]); +function onFileReceived(sessionId, timestamp, uuid, file_name) { + msgHistory.get(sessionId).push([false, timestamp, true, [uuid, file_name]]); onMsgOrFileReceived(sessionId, false, file_name); } -function onFileSent(sessionId, uuid, file_name) { - msgHistory.get(sessionId).push([true, true, [uuid, file_name]]); +function onFileSent(sessionId, timestamp, uuid, file_name) { + msgHistory.get(sessionId).push([true, timestamp, true, [uuid, file_name]]); if (currentSessionId == sessionId) { displayHistory(); } @@ -1030,18 +1043,25 @@ function generateMsgHeader(name, sessionId) { div.appendChild(p); return div; } +function generateMessageTimestamp(timestamp) { + let p = document.createElement("p"); + p.classList.add("timestamp"); + p.title = timestamp; + p.textContent = toTwoNumbers(timestamp.getHours())+":"+toTwoNumbers(timestamp.getMinutes()); + return p; +} function generateMessage(name, sessionId, msg) { let p = document.createElement("p"); p.appendChild(document.createTextNode(msg)); let div = document.createElement("div"); div.classList.add("content"); div.appendChild(linkifyElement(p)); - let li = document.createElement("li"); + let divContainer = document.createElement("div"); if (typeof name !== "undefined") { - li.appendChild(generateMsgHeader(name, sessionId)); + divContainer.appendChild(generateMsgHeader(name, sessionId)); } - li.appendChild(div); - return li; + divContainer.appendChild(div); + return divContainer; } function generateFile(name, sessionId, outgoing, file_info) { let div1 = document.createElement("div"); @@ -1063,12 +1083,12 @@ function generateFile(name, sessionId, outgoing, file_info) { a.href = "/load_file?uuid="+file_info[0]+"&file_name="+encodeURIComponent(file_info[1]); a.target = "_blank"; div1.appendChild(a); - let li = document.createElement("li"); + let divContainer = document.createElement("div"); if (typeof name !== "undefined") { - li.appendChild(generateMsgHeader(name, sessionId)); + divContainer.appendChild(generateMsgHeader(name, sessionId)); } - li.appendChild(div1); - return li; + divContainer.appendChild(div1); + return divContainer; } function generateFileInfo(fileName, fileSize, p) { let span = document.createElement("span"); @@ -1152,11 +1172,16 @@ function displayHistory(scrollToBottom = true) { sessionId = currentSessionId; } } - if (entry[1]) { //is file - msg_log.appendChild(generateFile(name, sessionId, entry[0], entry[2])); + let div; + if (entry[2]) { //is file + div = generateFile(name, sessionId, entry[0], entry[3]); } else { - msg_log.appendChild(generateMessage(name, sessionId, entry[2])); + div = generateMessage(name, sessionId, entry[3]); } + let li = document.createElement("li"); + li.appendChild(div); + li.appendChild(generateMessageTimestamp(entry[1])); + msg_log.appendChild(li); }); if (scrollToBottom) { msg_log.scrollTop = msg_log.scrollHeight; diff --git a/src/identity.rs b/src/identity.rs index b584010..ddc28cc 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -43,12 +43,11 @@ fn get_database_path() -> String { AppDirs::new(Some(constants::APPLICATION_FOLDER), false).unwrap().data_dir.join(constants::DB_NAME).to_str().unwrap().to_owned() } -struct EncryptedIdentity { - name: String, - encrypted_keypair: Vec, - salt: Vec, - encrypted_master_key: Vec, - encrypted_use_padding: Vec, +#[derive(Debug, Clone)] +pub struct Message { + pub outgoing: bool, + pub timestamp: u64, + pub data: Vec, } pub struct Contact { @@ -60,6 +59,14 @@ pub struct Contact { pub seen: bool, } +struct EncryptedIdentity { + name: String, + encrypted_keypair: Vec, + salt: Vec, + encrypted_master_key: Vec, + encrypted_use_padding: Vec, +} + pub struct Identity { pub name: String, pub keypair: Keypair, @@ -242,16 +249,17 @@ impl Identity { Ok(file_uuid) } - pub fn store_msg(&self, contact_uuid: &Uuid, outgoing: bool, data: &[u8]) -> Result { + pub fn store_msg(&self, contact_uuid: &Uuid, message: Message) -> Result { let db = Connection::open(get_database_path())?; - db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, data BLOB)", contact_uuid), [])?; - let outgoing_byte: u8 = bool_to_byte(outgoing); + db.execute(&format!("CREATE TABLE IF NOT EXISTS \"{}\" (outgoing BLOB, timestamp BLOB, data BLOB)", contact_uuid), [])?; + let outgoing_byte: u8 = bool_to_byte(message.outgoing); let encrypted_outgoing = crypto::encrypt_data(&[outgoing_byte], &self.master_key).unwrap(); - let encrypted_data = crypto::encrypt_data(data, &self.master_key).unwrap(); - db.execute(&format!("INSERT INTO \"{}\" (outgoing, data) VALUES (?1, ?2)", contact_uuid), params![encrypted_outgoing, encrypted_data]) + let encrypted_timestamp = crypto::encrypt_data(&message.timestamp.to_be_bytes(), &self.master_key).unwrap(); + let encrypted_data = crypto::encrypt_data(&message.data, &self.master_key).unwrap(); + db.execute(&format!("INSERT INTO \"{}\" (outgoing, timestamp, data) VALUES (?1, ?2, ?3)", contact_uuid), params![encrypted_outgoing, encrypted_timestamp, encrypted_data]) } - pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option)>> { + pub fn load_msgs(&self, contact_uuid: &Uuid, offset: usize, mut count: usize) -> Option> { match Connection::open(get_database_path()) { Ok(db) => { if let Ok(mut stmt) = db.prepare(&format!("SELECT count(*) FROM \"{}\"", contact_uuid)) { @@ -262,7 +270,7 @@ impl Identity { if offset+count >= total { count = total-offset; } - let mut stmt = db.prepare(&format!("SELECT outgoing, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap(); + let mut stmt = db.prepare(&format!("SELECT outgoing, timestamp, data FROM \"{}\" LIMIT {} OFFSET {}", contact_uuid, count, total-offset-count)).unwrap(); let mut rows = stmt.query([]).unwrap(); let mut msgs = Vec::new(); while let Ok(Some(row)) = rows.next() { @@ -271,9 +279,19 @@ impl Identity { Ok(outgoing) => { match byte_to_bool(outgoing[0]) { Ok(outgoing) => { - let encrypted_data: Vec = row.get(1).unwrap(); - match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) { - Ok(data) => msgs.push((outgoing, data)), + let encrypted_timestamp: Vec = row.get(1).unwrap(); + match crypto::decrypt_data(&encrypted_timestamp, &self.master_key) { + Ok(timestamp) => { + let encrypted_data: Vec = row.get(2).unwrap(); + match crypto::decrypt_data(encrypted_data.as_slice(), &self.master_key) { + Ok(data) => msgs.push(Message { + outgoing, + timestamp: u64::from_be_bytes(timestamp.try_into().unwrap()), + data, + }), + Err(e) => print_error!(e) + } + } Err(e) => print_error!(e) } } diff --git a/src/main.rs b/src/main.rs index 5899728..7449730 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use image::GenericImageView; use tokio::{net::TcpListener, runtime::Handle, sync::mpsc}; use actix_web::{App, HttpMessage, HttpRequest, HttpResponse, HttpServer, http::{header, CookieBuilder}, web, web::Data}; use actix_multipart::Multipart; -use tungstenite::Message; use futures::{StreamExt, TryStreamExt}; use rand::{RngCore, rngs::OsRng}; use serde::{Deserialize, Serialize}; @@ -24,6 +23,7 @@ use utils::escape_double_quote; use identity::Identity; use session_manager::{SessionManager, SessionCommand}; use ui_interface::UiConnection; +use crate::{identity::Message, utils::get_unix_timestamp_sec}; async fn start_websocket_server(global_vars: Arc>) -> u16 { let websocket_bind_addr = env::var("AIRA_WEBSOCKET_ADDR").unwrap_or("127.0.0.1".to_owned()); @@ -137,7 +137,7 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc { if msg.is_ping() { - ui_connection.write_message(Message::Pong(Vec::new())); //not sure if I'm doing this right + ui_connection.write_message(tungstenite::Message::Pong(Vec::new())); //not sure if I'm doing this right } else if msg.is_text() { let msg = msg.into_text().unwrap(); let mut ui_connection = ui_connection.clone(); @@ -161,10 +161,15 @@ async fn websocket_worker(mut ui_connection: UiConnection, global_vars: Arc { let session_id: usize = args[1].parse().unwrap(); let buffer = protocol::new_message(msg[args[0].len()+args[1].len()+2..].to_string()); + let timestamp = get_unix_timestamp_sec(); if session_manager.send_command(&session_id, SessionCommand::Send { buff: buffer.clone() }).await { - session_manager.store_msg(&session_id, true, buffer); + session_manager.store_msg(&session_id, Message { + outgoing: true, + timestamp, + data: buffer, + }); } } "large_files" => { @@ -435,13 +440,18 @@ async fn handle_send_file(req: HttpRequest, mut payload: Multipart) -> HttpRespo while let Some(Ok(chunk)) = field.next().await { buffer.extend(chunk); } + let timestamp = get_unix_timestamp_sec(); if global_vars_read.session_manager.send_command(&session_id, SessionCommand::Send { buff: protocol::file(filename, &buffer) }).await { match global_vars_read.session_manager.store_file(&session_id, &buffer) { Ok(file_uuid) => { let msg = [&[protocol::Headers::FILE][..], file_uuid.as_bytes(), filename.as_bytes()].concat(); - global_vars_read.session_manager.store_msg(&session_id, true, msg); + global_vars_read.session_manager.store_msg(&session_id, Message { + outgoing: true, + timestamp, + data: msg, + }); return HttpResponse::Ok().body(file_uuid.to_string()); } Err(e) => print_error!(e) diff --git a/src/session_manager.rs b/src/session_manager.rs index 28c4f03..1d30103 100644 --- a/src/session_manager.rs +++ b/src/session_manager.rs @@ -4,8 +4,7 @@ use libmdns::Service; use uuid::Uuid; use platform_dirs::UserDirs; use async_psec::{PUBLIC_KEY_LENGTH, Session, SessionWriteHalf, PsecWriter, PsecReader, PsecError}; -use crate::{constants, protocol, crypto, discovery, identity::{Contact, Identity}, print_error, utils::{get_unix_timestamp, get_not_used_path}}; -use crate::ui_interface::UiConnection; +use crate::{constants, crypto, discovery, identity::{Contact, Identity, Message}, ui_interface::UiConnection, print_error, protocol, utils::{get_not_used_path, get_unix_timestamp_ms, get_unix_timestamp_sec}}; pub enum SessionCommand { Send { @@ -55,7 +54,7 @@ pub struct SessionManager { ui_connection: Mutex>, loaded_contacts: RwLock>, pub last_loaded_msg_offsets: RwLock>, - pub saved_msgs: RwLock)>>>, + pub saved_msgs: RwLock>>, pub not_seen: RwLock>, mdns_service: Mutex>, listener_stop_signal: Mutex>>, @@ -88,11 +87,11 @@ impl SessionManager { Ok(()) } - pub fn store_msg(&self, session_id: &usize, outgoing: bool, buffer: Vec) { + pub fn store_msg(&self, session_id: &usize, message: Message) { let mut msg_saved = false; if let Some(contact) = self.loaded_contacts.read().unwrap().get(session_id) { let mut offsets = self.last_loaded_msg_offsets.write().unwrap(); //locking mutex before modifying the DB to prevent race conditions with load_msgs() - match self.identity.read().unwrap().as_ref().unwrap().store_msg(&contact.uuid, outgoing, &buffer) { + match self.identity.read().unwrap().as_ref().unwrap().store_msg(&contact.uuid, message.clone()) { Ok(_) => { *offsets.get_mut(session_id).unwrap() += 1; msg_saved = true; @@ -101,7 +100,7 @@ impl SessionManager { } } if !msg_saved { - self.saved_msgs.write().unwrap().get_mut(&session_id).unwrap().push((outgoing, buffer)); + self.saved_msgs.write().unwrap().get_mut(&session_id).unwrap().push(message); } } @@ -151,7 +150,7 @@ impl SessionManager { }); } - async fn send_msg(&self, session_id: usize, session_write: &mut SessionWriteHalf, buff: &[u8], is_sending: &mut bool, file_ack_sender: &mut Option>) -> Result<(), PsecError> { + async fn send_msg(&self, session_id: usize, session_write: &mut SessionWriteHalf, buff: Vec, is_sending: &mut bool, file_ack_sender: &mut Option>) -> Result<(), PsecError> { self.encrypt_and_send(session_write, &buff).await?; if buff[0] == protocol::Headers::ACCEPT_LARGE_FILES { self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download.as_mut().unwrap().accepted = true; @@ -166,7 +165,7 @@ impl SessionManager { } } self.with_ui_connection(|ui_connection| { - ui_connection.on_msg_sent(session_id, &buff); + ui_connection.on_msg_sent(session_id, get_unix_timestamp_sec(), buff); }); Ok(()) } @@ -223,7 +222,7 @@ impl SessionManager { let mut sessions = self.sessions.write().unwrap(); let files_transfer = sessions.get_mut(&session_id).unwrap().files_download.as_mut().unwrap(); let file_transfer = &mut files_transfer.files[files_transfer.index]; - file_transfer.last_chunk = get_unix_timestamp(); + file_transfer.last_chunk = get_unix_timestamp_ms(); file_transfer.transferred += chunk_size; if file_transfer.transferred >= file_transfer.file_size { //we downloaded all the file if files_transfer.index+1 == files_transfer.files.len() { @@ -281,7 +280,7 @@ impl SessionManager { self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = None; local_file_handle = None; self.with_ui_connection(|ui_connection| { - ui_connection.on_received(&session_id, &buffer); + ui_connection.on_received(&session_id, get_unix_timestamp_sec(), buffer); }); } protocol::Headers::ASK_LARGE_FILES => { @@ -293,7 +292,7 @@ impl SessionManager { file_name: info.1, file_size: info.0, transferred: 0, - last_chunk: get_unix_timestamp(), + last_chunk: get_unix_timestamp_ms(), } }).collect(); self.sessions.write().unwrap().get_mut(&session_id).unwrap().files_download = Some(LargeFilesDownload { @@ -389,8 +388,9 @@ impl SessionManager { Some(buffer) } }; - if buffer.is_some() { + if let Some(buffer) = buffer { let is_classical_message = header == protocol::Headers::MESSAGE || header == protocol::Headers::FILE; + let timestamp = get_unix_timestamp_sec(); if is_classical_message { self.set_seen(session_id, false); } else if header == protocol::Headers::ACCEPT_LARGE_FILES { @@ -398,10 +398,14 @@ impl SessionManager { last_chunks_sizes = Some(Vec::new()); } self.with_ui_connection(|ui_connection| { - ui_connection.on_received(&session_id, buffer.as_ref().unwrap()); + ui_connection.on_received(&session_id, timestamp, buffer.clone()); }); if is_classical_message { - self.store_msg(&session_id, false, buffer.unwrap()); + self.store_msg(&session_id, Message { + outgoing: false, + timestamp, + data: buffer, + }); } } } @@ -422,7 +426,7 @@ impl SessionManager { if is_sending { msg_queue.push(buff); } else { - if let Err(e) = self.send_msg(session_id, &mut session_write, &buff, &mut is_sending, &mut file_ack_sender).await { + if let Err(e) = self.send_msg(session_id, &mut session_write, buff, &mut is_sending, &mut file_ack_sender).await { print_error!(e); break; } @@ -440,7 +444,7 @@ impl SessionManager { //once the pre-encrypted chunk is sent, we can send the pending messages while msg_queue.len() > 0 { let msg = msg_queue.remove(0); - if let Err(e) = self.send_msg(session_id, &mut session_write, &msg, &mut is_sending, &mut file_ack_sender).await { + if let Err(e) = self.send_msg(session_id, &mut session_write, msg, &mut is_sending, &mut file_ack_sender).await { print_error!(e); break; } @@ -583,7 +587,7 @@ impl SessionManager { self.loaded_contacts.read().unwrap().iter().map(|c| (*c.0, c.1.name.clone(), c.1.verified, c.1.public_key)).collect() } - pub fn get_saved_msgs(&self) -> HashMap)>> { + pub fn get_saved_msgs(&self) -> HashMap> { self.saved_msgs.read().unwrap().clone() } @@ -659,7 +663,7 @@ impl SessionManager { }, data) } - pub fn load_msgs(&self, session_id: &usize, count: usize) -> Option)>> { + pub fn load_msgs(&self, session_id: &usize, count: usize) -> Option> { let mut offsets = self.last_loaded_msg_offsets.write().unwrap(); let msgs = self.identity.read().unwrap().as_ref().unwrap().load_msgs( &self.loaded_contacts.read().unwrap().get(session_id)?.uuid, diff --git a/src/ui_interface.rs b/src/ui_interface.rs index 3393bb3..aff0f35 100644 --- a/src/ui_interface.rs +++ b/src/ui_interface.rs @@ -1,12 +1,12 @@ -use std::net::{IpAddr, TcpStream}; +use std::{net::{IpAddr, TcpStream}}; use tungstenite::{WebSocket, protocol::Role, Message}; -use crate::{protocol, session_manager::{LargeFileDownload, LargeFilesDownload}}; +use crate::{identity, protocol, session_manager::{LargeFileDownload, LargeFilesDownload}}; mod ui_messages { use std::{fmt::Display, iter::FromIterator, net::IpAddr, str::from_utf8}; use tungstenite::Message; use uuid::Uuid; - use crate::{print_error, session_manager::{LargeFileDownload, LargeFilesDownload}, protocol, utils::to_uuid_bytes}; + use crate::{identity, print_error, protocol, session_manager::{LargeFileDownload, LargeFilesDownload}, utils::to_uuid_bytes}; fn simple_event(command: &str, session_id: &usize) -> Message { Message::from(format!("{} {}", command, session_id)) @@ -23,10 +23,10 @@ mod ui_messages { pub fn on_new_session(session_id: &usize, name: &str, outgoing: bool, fingerprint: &str, ip: IpAddr) -> Message { Message::from(format!("new_session {} {} {} {} {}", session_id, outgoing, fingerprint, ip, name)) } - pub fn on_file_received(session_id: &usize, buffer: &[u8]) -> Option { + pub fn on_file_received(session_id: &usize, timestamp: u64, buffer: &[u8]) -> Option { let uuid = Uuid::from_bytes(to_uuid_bytes(&buffer[1..17])?); match from_utf8(&buffer[17..]) { - Ok(file_name) => Some(Message::from(format!("file {} {} {}", session_id, uuid.to_string(), file_name))), + Ok(file_name) => Some(Message::from(format!("file {} {} {} {}", session_id, timestamp, uuid.to_string(), file_name))), Err(e) => { print_error!(e); None @@ -71,9 +71,9 @@ mod ui_messages { pub fn on_file_transfer_aborted(session_id: &usize) -> Message { simple_event("aborted", session_id) } - pub fn on_new_message(session_id: &usize, outgoing: bool, buffer: &[u8]) -> Option { - match from_utf8(&buffer[1..]) { - Ok(msg) => Some(Message::from(format!("{} {} {} {}", "new_message", session_id, outgoing, msg))), + pub fn on_new_message(session_id: &usize, message: identity::Message) -> Option { + match from_utf8(&message.data[1..]) { + Ok(msg) => Some(Message::from(format!("new_message {} {} {} {}", session_id, message.outgoing, message.timestamp, msg))), Err(e) => { print_error!(e); None @@ -83,18 +83,18 @@ mod ui_messages { pub fn inc_files_transfer(session_id: &usize, chunk_size: u64) -> Message { Message::from(format!("inc_file_transfer {} {}", session_id, chunk_size)) } - pub fn load_msgs(session_id: &usize, msgs: &Vec<(bool, Vec)>) -> Message { + pub fn load_msgs(session_id: &usize, msgs: &Vec) -> Message { let mut s = format!("load_msgs {}", session_id); - msgs.into_iter().rev().for_each(|entry| { - match entry.1[0] { - protocol::Headers::MESSAGE => match from_utf8(&entry.1[1..]) { - Ok(msg) => s.push_str(&format!(" m {} {}", entry.0, base64::encode(msg))), + msgs.into_iter().rev().for_each(|message| { + match message.data[0] { + protocol::Headers::MESSAGE => match from_utf8(&message.data[1..]) { + Ok(msg) => s.push_str(&format!(" m {} {} {}", message.outgoing, message.timestamp, base64::encode(msg))), Err(e) => print_error!(e) } protocol::Headers::FILE => { - let uuid = Uuid::from_bytes(to_uuid_bytes(&entry.1[1..17]).unwrap()); - match from_utf8(&entry.1[17..]) { - Ok(file_name) => s.push_str(&format!(" f {} {} {}", entry.0, uuid.to_string(), base64::encode(file_name))), + let uuid = Uuid::from_bytes(to_uuid_bytes(&message.data[1..17]).unwrap()); + match from_utf8(&message.data[17..]) { + Ok(file_name) => s.push_str(&format!(" f {} {} {} {}", message.outgoing, message.timestamp, uuid.to_string(), base64::encode(file_name))), Err(e) => print_error!(e) } } @@ -148,10 +148,14 @@ impl UiConnection { } } - pub fn on_received(&mut self, session_id: &usize, buffer: &[u8]) { + pub fn on_received(&mut self, session_id: &usize, timestamp: u64, buffer: Vec) { let ui_message = match buffer[0] { - protocol::Headers::MESSAGE => ui_messages::on_new_message(session_id, false, buffer), - protocol::Headers::FILE => ui_messages::on_file_received(session_id, buffer), + protocol::Headers::MESSAGE => ui_messages::on_new_message(session_id, identity::Message { + outgoing: false, + timestamp, + data: buffer + }), + protocol::Headers::FILE => ui_messages::on_file_received(session_id, timestamp, &buffer), protocol::Headers::ACCEPT_LARGE_FILES => Some(ui_messages::on_large_files_accepted(session_id)), protocol::Headers::ABORT_FILES_TRANSFER => Some(ui_messages::on_file_transfer_aborted(session_id)), _ => None @@ -163,9 +167,13 @@ impl UiConnection { pub fn on_ask_large_files(&mut self, session_id: &usize, files: &Vec, download_location: &str) { self.write_message(ui_messages::on_ask_large_files(session_id, files, download_location)) } - pub fn on_msg_sent(&mut self, session_id: usize, buffer: &[u8]) { + pub fn on_msg_sent(&mut self, session_id: usize, timestamp: u64, buffer: Vec) { match buffer[0] { - protocol::Headers::MESSAGE => match ui_messages::on_new_message(&session_id, true, buffer) { + protocol::Headers::MESSAGE => match ui_messages::on_new_message(&session_id, identity::Message { + outgoing: true, + timestamp, + data: buffer + }) { Some(msg) => self.write_message(msg), None => {} } @@ -195,7 +203,7 @@ impl UiConnection { pub fn set_as_contact(&mut self, session_id: usize, name: &str, verified: bool, fingerprint: &str) { self.write_message(ui_messages::set_as_contact(session_id, name, verified, fingerprint)); } - pub fn load_msgs(&mut self, session_id: &usize, msgs: &Vec<(bool, Vec)>) { + pub fn load_msgs(&mut self, session_id: &usize, msgs: &Vec) { self.write_message(ui_messages::load_msgs(session_id, msgs)); } pub fn set_not_seen(&mut self, session_ids: Vec) { diff --git a/src/utils.rs b/src/utils.rs index 72e751a..afb4e88 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,10 +16,14 @@ pub fn escape_double_quote(origin: String) -> String { origin.replace("\"", "\\\"") } -pub fn get_unix_timestamp() -> u128 { +pub fn get_unix_timestamp_ms() -> u128 { SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() } +pub fn get_unix_timestamp_sec() -> u64 { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() +} + pub fn get_not_used_path(file_name: &str, parent_directory: &PathBuf) -> String { let has_extension = file_name.matches('.').count() > 0; let mut path = parent_directory.join(&file_name);