Encrypted peer-to-peer IM for data security. Own data, own privacy. (Rust+Flutter)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

293 lines
9.5 KiB

use std::path::PathBuf;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::{EventId, GroupId},
primitive::{PeerId, Result},
rpc::{json, RpcParam},
};
use tdn_storage::local::{DStorage, DsValue};
use chat_types::{MessageType, NetworkMessage};
use crate::storage::{
read_avatar_sync, read_file_sync, read_image_sync, read_record_sync, write_avatar_sync,
write_file_sync, write_image_sync, write_record_sync,
};
pub(crate) fn handle_nmsg(
nmsg: NetworkMessage,
is_me: bool,
gid: GroupId,
base: &PathBuf,
db: &DStorage,
fid: i64,
hash: EventId,
) -> Result<(Message, String)> {
// handle event.
let (m_type, raw) = match nmsg {
NetworkMessage::String(content) => (MessageType::String, content),
NetworkMessage::Image(bytes) => {
let image_name = write_image_sync(base, &gid, bytes)?;
(MessageType::Image, image_name)
}
NetworkMessage::File(old_name, bytes) => {
let filename = write_file_sync(base, &gid, &old_name, bytes)?;
(MessageType::File, filename)
}
NetworkMessage::Contact(name, rgid, addr, avatar_bytes) => {
write_avatar_sync(base, &gid, &rgid, avatar_bytes)?;
let tmp_name = name.replace(";", "-;");
let contact_values = format!("{};;{};;{}", tmp_name, rgid.to_hex(), addr.to_hex());
(MessageType::Contact, contact_values)
}
NetworkMessage::Emoji => {
// TODO
(MessageType::Emoji, "".to_owned())
}
NetworkMessage::Record(bytes, time) => {
let record_name = write_record_sync(base, &gid, fid, time, bytes)?;
(MessageType::Record, record_name)
}
NetworkMessage::Phone => {
// TODO
(MessageType::Phone, "".to_owned())
}
NetworkMessage::Video => {
// TODO
(MessageType::Video, "".to_owned())
}
NetworkMessage::Invite(content) => (MessageType::Invite, content),
};
let scontent = match m_type {
MessageType::String => {
format!("{}:{}", m_type.to_int(), raw)
}
_ => format!("{}:", m_type.to_int()),
};
let mut msg = Message::new_with_id(hash, fid, is_me, m_type, raw, true);
msg.insert(db)?;
Ok((msg, scontent))
}
pub fn from_model(base: &PathBuf, gid: &GroupId, model: Message) -> Result<NetworkMessage> {
// handle message's type.
match model.m_type {
MessageType::String => Ok(NetworkMessage::String(model.content)),
MessageType::Image => {
let bytes = read_image_sync(base, gid, &model.content)?;
Ok(NetworkMessage::Image(bytes))
}
MessageType::File => {
let bytes = read_file_sync(base, gid, &model.content)?;
Ok(NetworkMessage::File(model.content, bytes))
}
MessageType::Contact => {
let v: Vec<&str> = model.content.split(";;").collect();
if v.len() != 3 {
return Err(anyhow!("message is invalid"));
}
let cname = v[0].to_owned();
let cgid = GroupId::from_hex(v[1])?;
let caddr = PeerId::from_hex(v[2])?;
let avatar_bytes = read_avatar_sync(base, gid, &cgid)?;
Ok(NetworkMessage::Contact(cname, cgid, caddr, avatar_bytes))
}
MessageType::Record => {
let (bytes, time) = if let Some(i) = model.content.find('-') {
let time = model.content[0..i].parse().unwrap_or(0);
let bytes = read_record_sync(base, gid, &model.content[i + 1..])?;
(bytes, time)
} else {
(vec![], 0)
};
Ok(NetworkMessage::Record(bytes, time))
}
MessageType::Invite => Ok(NetworkMessage::Invite(model.content)),
MessageType::Emoji => Ok(NetworkMessage::Emoji),
MessageType::Phone => Ok(NetworkMessage::Phone),
MessageType::Video => Ok(NetworkMessage::Video),
}
}
pub(crate) struct Message {
pub id: i64,
pub hash: EventId,
pub fid: i64,
pub is_me: bool,
pub m_type: MessageType,
pub content: String,
pub is_delivery: bool,
pub datetime: i64,
pub is_deleted: bool,
}
impl Message {
pub fn new(
gid: &GroupId,
fid: i64,
is_me: bool,
m_type: MessageType,
content: String,
is_delivery: bool,
) -> Message {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
let mut bytes = [0u8; 32];
bytes[0..8].copy_from_slice(&gid.0[0..8]);
bytes[8..16].copy_from_slice(&(fid as u64).to_le_bytes()); // 8-bytes.
bytes[16..24].copy_from_slice(&(datetime as u64).to_le_bytes()); // 8-bytes.
let content_bytes = content.as_bytes();
if content_bytes.len() >= 8 {
bytes[24..32].copy_from_slice(&content_bytes[0..8]);
} else {
bytes[24..(24 + content_bytes.len())].copy_from_slice(&content_bytes);
}
Message {
id: 0,
hash: EventId(bytes),
is_deleted: false,
fid,
is_me,
m_type,
content,
is_delivery,
datetime,
}
}
pub fn new_with_id(
hash: EventId,
fid: i64,
is_me: bool,
m_type: MessageType,
content: String,
is_delivery: bool,
) -> Message {
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_secs())
.unwrap_or(0) as i64; // safe for all life.
Message {
id: 0,
is_deleted: false,
hash,
fid,
is_me,
m_type,
content,
is_delivery,
datetime,
}
}
/// here is zero-copy and unwrap is safe. checked.
fn from_values(mut v: Vec<DsValue>, contains_deleted: bool) -> Message {
let is_deleted = if contains_deleted {
v.pop().unwrap().as_bool()
} else {
false
};
Message {
is_deleted,
datetime: v.pop().unwrap().as_i64(),
is_delivery: v.pop().unwrap().as_bool(),
content: v.pop().unwrap().as_string(),
m_type: MessageType::from_int(v.pop().unwrap().as_i64()),
is_me: v.pop().unwrap().as_bool(),
fid: v.pop().unwrap().as_i64(),
hash: EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default()),
id: v.pop().unwrap().as_i64(),
}
}
pub fn to_rpc(&self) -> RpcParam {
json!([
self.id,
self.hash.to_hex(),
self.fid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
])
}
pub fn get(db: &DStorage, fid: &i64) -> Result<Vec<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime FROM messages WHERE fid = {} and is_deleted = false ORDER BY id DESC", fid);
let matrix = db.query(&sql)?;
let mut messages = vec![];
for values in matrix {
messages.push(Message::from_values(values, false));
}
Ok(messages)
}
pub fn get_id(db: &DStorage, id: i64) -> Result<Option<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE id = {}", id);
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Message::from_values(values, true)));
}
Ok(None)
}
pub fn get_it(db: &DStorage, hash: &EventId) -> Result<Option<Message>> {
let sql = format!("SELECT id, hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted FROM messages WHERE hash = {}", hash.to_hex());
let mut matrix = db.query(&sql)?;
if matrix.len() > 0 {
let values = matrix.pop().unwrap(); // safe unwrap()
return Ok(Some(Message::from_values(values, true)));
}
Ok(None)
}
pub fn insert(&mut self, db: &DStorage) -> Result<()> {
let sql = format!(
"INSERT INTO messages (hash, fid, is_me, m_type, content, is_delivery, datetime, is_deleted) VALUES ('{}',{},{},{},'{}',{},{},false)",
self.hash.to_hex(),
self.fid,
self.is_me,
self.m_type.to_int(),
self.content,
self.is_delivery,
self.datetime,
);
self.id = db.insert(&sql)?;
Ok(())
}
pub fn delivery(db: &DStorage, id: i64, is_delivery: bool) -> Result<usize> {
let sql = format!(
"UPDATE messages SET is_delivery={} WHERE id = {}",
is_delivery, id,
);
db.update(&sql)
}
pub fn delete(&self, db: &DStorage) -> Result<usize> {
let sql = format!(
"UPDATE messages SET is_deleted = true WHERE id = {}",
self.id
);
db.delete(&sql)
}
pub fn exist(db: &DStorage, hash: &EventId) -> Result<bool> {
let sql = format!("SELECT id FROM messages WHERE hash = '{}'", hash.to_hex());
let matrix = db.query(&sql)?;
Ok(matrix.len() > 0)
}
}