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.
 
 
 
 
 
 

872 lines
32 KiB

use esse_primitives::NetworkMessage;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use tdn::types::{
group::{EventId, GroupId},
message::{SendMessage, SendType},
primitive::{HandleResult, PeerId, Result},
};
use tdn_storage::local::DStorage;
use tokio::sync::{mpsc::Sender, RwLock};
use crate::account::{Account, User};
use crate::apps::chat::LayerEvent;
use crate::consensus::Event as OldEvent;
use crate::group::{Group, GroupEvent};
use crate::layer::Layer;
use crate::migrate::consensus::{
ACCOUNT_TABLE_PATH, FILE_TABLE_PATH, FRIEND_TABLE_PATH, MESSAGE_TABLE_PATH, REQUEST_TABLE_PATH,
};
use crate::apps::chat::rpc as chat_rpc;
use crate::apps::chat::{from_model, Friend, Message, Request};
use crate::apps::file::{FileDid, RootDirectory};
use crate::rpc;
use crate::storage::{delete_avatar_sync, read_avatar_sync, write_avatar_sync};
/// Online state synchronization.
#[derive(Serialize, Deserialize)]
pub(crate) enum State {
Account,
Session,
ChatMessage,
GroupMessage,
}
pub(crate) async fn _handle_state(
_gid: GroupId,
_addr: PeerId,
state: State,
_group: &Arc<RwLock<Group>>,
_layer: &Arc<RwLock<Layer>>,
_results: &mut HandleResult,
) -> Result<()> {
match state {
State::Account => {}
State::Session => {}
State::ChatMessage => {}
State::GroupMessage => {}
}
Ok(())
}
/// Event that will update data.
#[derive(Serialize, Deserialize)]
pub(crate) enum InnerEvent {
/// account info update.
/// params: name, avatar.
UserInfo(String, Vec<u8>),
/// Session's request create.
/// params: is_me, remote, remark.
SessionRequestCreate(bool, User, String),
/// Session's request agree or reject, if agree with avatar. friend's addr.
SessionRequestHandle(GroupId, bool, Vec<u8>),
/// Session's request delete.
SessionRequestDelete(GroupId),
/// Session's friend update by friend.
/// params: f_gid, addr, name, avatar. (TODO addrs as list to store. keep others device)
SessionFriendInfo(GroupId, PeerId, String, Vec<u8>),
/// Session's friend update by me.
/// params: f_gid, remark
SessionFriendUpdate(GroupId, String),
/// Session's friend close.
/// params: f_gid.
SessionFriendClose(GroupId),
/// Sesson's friend delete.
SessionFriendDelete(GroupId),
/// Session's message create.
/// params: f_gid, is_me, message_type,
SessionMessageCreate(GroupId, bool, EventId, NetworkMessage),
/// Session's message delete.
SessionMessageDelete(EventId),
/// create a file.
/// params: file_id, file_parent_id, file_directory, file_name, file_desc, device_addr.
FileCreate(FileDid, FileDid, RootDirectory, String, String, PeerId),
/// update file info. file_id, file_name, file_desc.
FileUpdate(FileDid, String, String),
/// update file's parent id (move file to other directory).
FileParent(FileDid, FileDid),
/// backup file in new device.
FileBackup(FileDid, PeerId),
/// delete a file.
FileDelete(FileDid),
}
/// Event that not update status. only change UI.
#[derive(Serialize, Deserialize)]
pub(crate) enum StatusEvent {
/// Session's friend online.
SessionFriendOnline(GroupId),
/// Session's friend offline.
SessionFriendOffline(GroupId),
}
/// event for sync models. use in sync consensus.
#[derive(Serialize, Deserialize)]
pub(crate) enum SyncEvent {
/// account info.
Account(EventId, String, Vec<u8>),
AccountHad(EventId),
/// eid, request_gid, addr, name, avatar, remark, is_me, is_ok
Request(
EventId,
GroupId,
PeerId,
String,
Vec<u8>,
String,
bool,
bool,
bool,
),
RequestHad(EventId, GroupId),
/// eid, friend_gid, addr, name, avatar, remark, is_closed
Friend(EventId, GroupId, PeerId, String, Vec<u8>, String, bool),
FriendHad(EventId, GroupId),
/// eid, friend_gid, msg_id, is_me, message.
Message(EventId, GroupId, EventId, bool, NetworkMessage),
None,
}
impl InnerEvent {
pub fn event_time(eid: &EventId) -> u128 {
let mut bytes = [0u8; 16];
bytes.copy_from_slice(&eid.0[0..16]);
u128::from_le_bytes(bytes)
}
pub fn generate_event_id(&self) -> EventId {
let mut bytes = [0u8; 32];
let start = SystemTime::now();
let datetime = start
.duration_since(UNIX_EPOCH)
.map(|s| s.as_nanos())
.unwrap_or(0);
bytes[0..16].copy_from_slice(&datetime.to_le_bytes());
let data = bincode::serialize(self).unwrap_or(vec![]);
bytes[16..32].copy_from_slice(&blake3::hash(&data).as_bytes()[0..16]);
EventId(bytes)
}
pub async fn direct_layer_session(
sender: Sender<SendMessage>,
layer: Arc<RwLock<Layer>>,
gid: GroupId,
fgid: GroupId,
event: LayerEvent,
) -> Result<()> {
let addr = layer.read().await.running(&gid)?.online_direct(&fgid)?;
let data = bincode::serialize(&event).unwrap_or(vec![]);
let msg = SendType::Event(0, addr, data);
let _ = sender.send(SendMessage::Layer(gid, fgid, msg)).await;
Ok(())
}
pub fn merge_event(
db: &DStorage,
addr: &PeerId,
results: &mut HandleResult,
our_height: u64,
our_event: EventId,
event_height: u64,
event_id: EventId,
need_sync: Option<GroupId>,
) -> Result<(u64, u64, EventId)> {
if our_height + 1 < event_height {
// TODO request for sync self_height + 1.
if let Some(gid) = need_sync {
let event = GroupEvent::SyncRequest(our_height + 1, event_height);
let data = bincode::serialize(&event).unwrap_or(vec![]);
results.groups.push((gid, SendType::Event(0, *addr, data)));
return Ok((event_height, our_height, our_event));
} else {
return Ok((event_height, event_height, event_id));
}
}
let mut merge_height = event_height;
if our_height >= event_height {
// load current event_hegiht.
let events = OldEvent::get_nexts(db, event_height)?;
for event in events {
let our_event_time = Self::event_time(&event.hash);
let remote_event_time = Self::event_time(&event_id);
if our_event_time > remote_event_time {
break;
}
if our_event_time == remote_event_time {
let mut remote_bytes = [0u8; 16];
remote_bytes.copy_from_slice(&event_id.0[16..32]);
let remote_next = u128::from_le_bytes(remote_bytes);
let mut our_bytes = [0u8; 16];
our_bytes.copy_from_slice(&event.hash.0[16..32]);
let our_next = u128::from_le_bytes(our_bytes);
if remote_next > our_next {
break;
}
}
merge_height = event.height;
}
}
Ok((merge_height, our_height + 1, our_event))
}
pub fn handle(
self,
group: &mut Group,
gid: GroupId,
addr: PeerId,
eheight: u64,
eid: EventId,
pre_event: EventId,
results: &mut HandleResult,
layer: &Arc<RwLock<Layer>>,
) -> Result<()> {
let account = group.account(&gid)?;
let db = group.consensus_db(&gid)?;
if OldEvent::contains_hash(&db, &eid)? {
return Ok(());
}
let (merge_height, next_height, next_eid) =
if account.own_height + 1 == eheight && account.event == pre_event {
(eheight, eheight, eid)
} else {
Self::merge_event(
&db,
&addr,
results,
account.own_height,
account.event,
eheight,
eid,
Some(gid),
)?
};
let (path, id) = match self {
InnerEvent::UserInfo(name, avatar) => {
results
.rpcs
.push(rpc::account_update(gid, &name, base64::encode(&avatar)));
group.update_account(gid, &name, avatar)?;
(ACCOUNT_TABLE_PATH, 0)
}
InnerEvent::SessionRequestCreate(is_me, remote, remark) => {
let db = group.chat_db(&gid)?;
// check if exist request.
if Friend::get_id(&db, &remote.id).is_ok() {
return Ok(());
}
if let Ok(req) = Request::get_id(&db, &remote.id) {
Request::delete(&db, &req.id)?; // delete the old request.
results.rpcs.push(chat_rpc::request_delete(gid, req.id));
}
let mut request =
Request::new(remote.id, remote.addr, remote.name, remark, is_me, true);
// save to db.
request.insert(&db)?;
drop(db);
// save the avatar.
write_avatar_sync(group.base(), &gid, &remote.id, remote.avatar)?;
results.rpcs.push(chat_rpc::request_create(gid, &request));
(REQUEST_TABLE_PATH, request.id)
}
InnerEvent::SessionRequestHandle(rgid, is_ok, avatar) => {
let db = group.chat_db(&gid)?;
if Friend::get_id(&db, &rgid).is_ok() {
return Ok(());
}
if let Ok(mut request) = Request::get_id(&db, &rgid) {
let rid = request.id;
request.is_over = true;
request.is_ok = is_ok;
request.update(&db)?;
if is_ok {
if avatar.len() > 0 {
write_avatar_sync(group.base(), &gid, &request.gid, avatar)?;
}
let friend = Friend::from_remote(
&db,
request.gid,
request.name,
request.addr,
"".to_owned(),
)?;
results
.rpcs
.push(chat_rpc::request_agree(gid, rid, &friend));
} else {
results.rpcs.push(chat_rpc::request_reject(gid, rid));
}
(REQUEST_TABLE_PATH, rid)
} else {
return Ok(());
}
}
InnerEvent::SessionRequestDelete(rgid) => {
let db = group.chat_db(&gid)?;
if let Ok(request) = Request::get_id(&db, &rgid) {
let rid = request.id;
Request::delete(&db, &request.id)?;
// delete avatar. check had friend.
if Friend::get_id(&db, &request.gid).is_err() {
delete_avatar_sync(group.base(), &gid, &request.gid)?;
}
results.rpcs.push(chat_rpc::request_delete(gid, rid));
(REQUEST_TABLE_PATH, rid)
} else {
return Ok(());
}
}
InnerEvent::SessionMessageCreate(rgid, is_me, hash, m) => {
let db = group.chat_db(&gid)?;
if Message::exist(&db, &hash)? {
return Ok(());
}
if let Ok(f) = Friend::get_id(&db, &rgid) {
if is_me {
let layer_lock = layer.clone();
let ggid = gid.clone();
let fgid = f.gid;
let sender = group.sender();
let layer_event = LayerEvent::Message(hash, m.clone());
tokio::spawn(InnerEvent::direct_layer_session(
sender,
layer_lock,
ggid,
fgid,
layer_event,
));
}
// Need Refactor
// let msg =
// handle_nmsg(group, m, is_me, gid, group.base(), &db, f.id, hash, results)
// .await?;
// results.rpcs.push(chat_rpc::message_create(gid, &msg));
(MESSAGE_TABLE_PATH, 0)
} else {
return Ok(());
}
}
InnerEvent::SessionMessageDelete(hash) => {
let db = group.chat_db(&gid)?;
if let Ok(m) = Message::get_by_hash(&db, &hash) {
Message::delete(&db, &m.id)?;
results.rpcs.push(chat_rpc::message_delete(gid, m.id));
(MESSAGE_TABLE_PATH, m.id)
} else {
return Ok(());
}
}
InnerEvent::SessionFriendInfo(rgid, raddr, rname, ravatar) => {
let db = group.chat_db(&gid)?;
if let Ok(mut f) = Friend::get_id(&db, &rgid) {
f.addr = raddr;
f.name = rname;
f.remote_update(&db)?;
if ravatar.len() > 0 {
write_avatar_sync(group.base(), &gid, &rgid, ravatar)?;
}
results.rpcs.push(chat_rpc::friend_info(gid, &f));
(FRIEND_TABLE_PATH, f.id)
} else {
return Ok(());
}
}
InnerEvent::SessionFriendUpdate(rgid, remark) => {
let db = group.chat_db(&gid)?;
if let Ok(mut f) = Friend::get_id(&db, &rgid) {
f.remark = remark;
f.me_update(&db)?;
results
.rpcs
.push(chat_rpc::friend_update(gid, f.id, &f.remark));
(FRIEND_TABLE_PATH, f.id)
} else {
return Ok(());
}
}
InnerEvent::SessionFriendClose(rgid) => {
let db = group.chat_db(&gid)?;
if let Ok(f) = Friend::get_id(&db, &rgid) {
f.close(&db)?;
results.rpcs.push(chat_rpc::friend_close(gid, f.id));
let rfid = f.id;
let layer_lock = layer.clone();
let ggid = gid.clone();
let sender = group.sender();
tokio::spawn(async move {
let online = layer_lock.write().await.remove_online(&ggid, &f.gid);
if let Some(faddr) = online {
let mut addrs: HashMap<PeerId, GroupId> = HashMap::new();
addrs.insert(faddr, f.gid);
tokio::spawn(rpc::sleep_waiting_close_stable(
sender,
HashMap::new(),
addrs,
));
}
});
(FRIEND_TABLE_PATH, rfid)
} else {
return Ok(());
}
}
InnerEvent::SessionFriendDelete(rgid) => {
let db = group.chat_db(&gid)?;
if let Ok(f) = Friend::get_id(&db, &rgid) {
Friend::delete(&db, &f.id)?;
results.rpcs.push(chat_rpc::friend_delete(gid, f.id));
delete_avatar_sync(group.base(), &gid, &f.gid)?;
let rfid = f.id;
let layer_lock = layer.clone();
let ggid = gid.clone();
let sender = group.sender();
tokio::spawn(async move {
let online = layer_lock.write().await.remove_online(&ggid, &f.gid);
if let Some(faddr) = online {
let mut addrs: HashMap<PeerId, GroupId> = HashMap::new();
addrs.insert(faddr, f.gid);
tokio::spawn(rpc::sleep_waiting_close_stable(
sender,
HashMap::new(),
addrs,
));
}
});
(FRIEND_TABLE_PATH, rfid)
} else {
return Ok(());
}
}
InnerEvent::FileCreate(_fid, _fpid, _ftype, _fname, _fdesc, _faddr) => {
// TOOD
(FILE_TABLE_PATH, 0)
}
InnerEvent::FileUpdate(_fid, _fname, _fdesc) => {
// TODO
(FILE_TABLE_PATH, 0)
}
InnerEvent::FileParent(_fid, _fpid) => {
// TODO
(FILE_TABLE_PATH, 0)
}
InnerEvent::FileBackup(_fid, _faddr) => {
// TODO
(FILE_TABLE_PATH, 0)
}
InnerEvent::FileDelete(_fid) => {
// TODO
(FILE_TABLE_PATH, 0)
}
};
OldEvent::merge(&db, eid, path, id, merge_height)?;
drop(db);
drop(layer);
let account_db = group.account_db()?;
let account = group.account_mut(&gid)?;
account.update_consensus(&account_db, next_height, next_eid)?;
account_db.close()?;
Ok(())
}
}
impl StatusEvent {
pub fn handle(
self,
group: &mut Group,
gid: GroupId,
addr: PeerId,
_results: &mut HandleResult,
layer: &Arc<RwLock<Layer>>,
_uid: u64,
) -> Result<()> {
match self {
StatusEvent::SessionFriendOnline(rgid) => {
let db = group.chat_db(&gid)?;
if let Ok(_f) = Friend::get_id(&db, &rgid) {
// TODO
}
}
StatusEvent::SessionFriendOffline(rgid) => {
let db = group.chat_db(&gid)?;
if let Ok(f) = Friend::get_id(&db, &rgid) {
let layer_lock = layer.clone();
let rgid = f.gid;
let _rid = f.id;
let ggid = gid.clone();
let _sender = group.sender();
tokio::spawn(async move {
if let Ok(running) = layer_lock.write().await.running_mut(&ggid) {
if running.check_offline(&rgid, &addr) {
// TODO
}
}
});
}
}
}
Ok(())
}
}
impl SyncEvent {
pub async fn sync(
group: &Group,
base: &PathBuf,
gid: &GroupId,
account: &Account,
from: u64,
to: u64,
) -> Result<Vec<Self>> {
let db = group.consensus_db(gid)?;
let sql = format!(
"SELECT id, hash, db_table, row from events WHERE id BETWEEN {} AND {}",
from, to
);
let matrix = db.query(&sql)?;
drop(db);
let mut pre_keys: Vec<(i64, i64)> = vec![];
let mut events: Vec<SyncEvent> = vec![];
let mut next = from;
for mut v in matrix {
let row = v.pop().unwrap().as_i64(); // safe
let path = v.pop().unwrap().as_i64(); // safe
let hash = EventId::from_hex(v.pop().unwrap().as_str()).unwrap_or(EventId::default());
let id = v.pop().unwrap().as_i64() as u64;
if id != next {
events.push(SyncEvent::None);
}
next += 1;
match path {
ACCOUNT_TABLE_PATH => {
if pre_keys.contains(&(path, row)) {
events.push(SyncEvent::AccountHad(hash));
continue;
} else {
pre_keys.push((path, row));
};
let name = account.name.clone();
let avatar = account.avatar.clone();
events.push(SyncEvent::Account(hash, name, avatar));
}
REQUEST_TABLE_PATH => {
let db = group.chat_db(gid)?;
let event = if let Ok(request) = Request::get(&db, &row) {
if pre_keys.contains(&(path, row)) {
events.push(SyncEvent::RequestHad(hash, request.gid));
continue;
} else {
pre_keys.push((path, row));
};
let avatar = if !request.is_ok {
vec![]
} else {
read_avatar_sync(base, gid, &request.gid)?
};
// request_gid, addr, name, avatar, remark, is_me, is_ok, is_delete
SyncEvent::Request(
hash,
request.gid,
request.addr,
request.name,
avatar,
request.remark,
request.is_me,
request.is_ok,
request.is_over,
)
} else {
SyncEvent::None
};
events.push(event);
}
FRIEND_TABLE_PATH => {
let db = group.chat_db(gid)?;
let event = if let Ok(friend) = Friend::get(&db, &row) {
if pre_keys.contains(&(path, row)) {
events.push(SyncEvent::FriendHad(hash, friend.gid));
continue;
} else {
pre_keys.push((path, row));
};
let avatar = if friend.is_closed {
vec![]
} else {
read_avatar_sync(base, gid, &friend.gid)?
};
SyncEvent::Friend(
hash,
friend.gid,
friend.addr,
friend.name,
avatar,
friend.remark,
friend.is_closed,
)
} else {
SyncEvent::None
};
events.push(event);
}
MESSAGE_TABLE_PATH => {
let db = group.chat_db(gid)?;
let event = if let Ok(msg) = Message::get(&db, &row) {
let fgid = if let Ok(f) = Friend::get(&db, &msg.fid) {
f.gid
} else {
GroupId::default()
};
// create
let mid = msg.hash;
let is_me = msg.is_me;
let nm = from_model(base, gid, msg).await?;
SyncEvent::Message(hash, fgid, mid, is_me, nm)
} else {
SyncEvent::None
};
events.push(event);
}
FILE_TABLE_PATH => {
//
}
_ => {}
}
}
if events.len() as u64 != to + 1 - from {
return Err(anyhow!("events number not matching."));
}
Ok(events)
}
pub fn handle(
gid: GroupId,
from: u64,
to: u64,
events: Vec<SyncEvent>,
group: &mut Group,
layer: &Arc<RwLock<Layer>>,
results: &mut HandleResult,
addr: PeerId,
) -> Result<()> {
if events.len() as u64 != to + 1 - from {
return Ok(());
}
let base = group.base().clone();
let consensus_db = group.consensus_db(&gid)?;
let mut next = from;
for event in events {
let height = next;
next += 1;
match &event {
SyncEvent::Account(eid, ..)
| SyncEvent::AccountHad(eid)
| SyncEvent::Request(eid, ..)
| SyncEvent::RequestHad(eid, ..)
| SyncEvent::Friend(eid, ..)
| SyncEvent::FriendHad(eid, ..)
| SyncEvent::Message(eid, ..) => {
if OldEvent::contains_hash(&consensus_db, eid)? {
continue;
}
}
SyncEvent::None => {
continue;
}
}
let (eid, path, id) = match event {
SyncEvent::Account(eid, name, avatar) => {
results
.rpcs
.push(rpc::account_update(gid, &name, base64::encode(&avatar)));
group.update_account(gid, &name, avatar)?;
(eid, ACCOUNT_TABLE_PATH, 0)
}
SyncEvent::AccountHad(eid) => (eid, ACCOUNT_TABLE_PATH, 0),
SyncEvent::Request(
eid,
rgid,
raddr,
rname,
avatar,
remark,
is_me,
is_ok,
is_over,
) => {
let chat_db = group.chat_db(&gid)?;
let request = if let Ok(mut req) = Request::get_id(&chat_db, &rgid) {
req.is_ok = is_ok;
req.is_over = is_over;
req.update(&chat_db)?;
req
} else {
let mut request = Request::new(rgid, raddr, rname, remark, is_me, true);
request.is_ok = is_ok;
request.is_over = is_over;
// save to db.
request.insert(&chat_db)?;
results.rpcs.push(chat_rpc::request_create(gid, &request));
request
};
let rid = request.id;
if is_ok {
if avatar.len() > 0 {
write_avatar_sync(&base, &gid, &request.gid, avatar)?;
}
let friend = Friend::from_remote(
&chat_db,
request.gid,
request.name,
request.addr,
"".to_owned(),
)?;
results
.rpcs
.push(chat_rpc::request_agree(gid, rid, &friend));
} else {
results.rpcs.push(chat_rpc::request_reject(gid, rid));
}
chat_db.close()?;
(eid, REQUEST_TABLE_PATH, rid)
}
SyncEvent::RequestHad(eid, rgid) => {
let chat_db = group.chat_db(&gid)?;
let id = if let Ok(req) = Request::get_id(&chat_db, &rgid) {
req.id
} else {
-1
};
(eid, REQUEST_TABLE_PATH, id)
}
SyncEvent::Friend(eid, fgid, faddr, fname, avatar, remark, is_closed) => {
let chat_db = group.chat_db(&gid)?;
let id = if let Ok(mut friend) = Friend::get_id(&chat_db, &fgid) {
friend.addr = faddr;
friend.name = fname;
friend.remark = remark;
friend.is_closed = is_closed;
friend.update(&chat_db)?;
if avatar.len() > 0 {
write_avatar_sync(&base, &gid, &friend.gid, avatar)?;
}
if friend.is_closed {
let layer_lock = layer.clone();
let ggid = gid.clone();
let fgid = friend.gid;
let sender = group.sender();
tokio::spawn(async move {
let online = layer_lock.write().await.remove_online(&ggid, &fgid);
if let Some(faddr) = online {
let mut addrs: HashMap<PeerId, GroupId> = HashMap::new();
addrs.insert(faddr, fgid);
tokio::spawn(rpc::sleep_waiting_close_stable(
sender,
HashMap::new(),
addrs,
));
}
});
}
results.rpcs.push(chat_rpc::friend_info(gid, &friend));
friend.id
} else {
-1
};
(eid, FRIEND_TABLE_PATH, id)
}
SyncEvent::FriendHad(eid, fgid) => {
let chat_db = group.chat_db(&gid)?;
let id = if let Ok(friend) = Friend::get_id(&chat_db, &fgid) {
friend.id
} else {
-1
};
(eid, FRIEND_TABLE_PATH, id)
}
SyncEvent::Message(eid, fgid, meid, _is_me, _m) => {
let chat_db = group.chat_db(&gid)?;
if Message::exist(&chat_db, &meid)? {
continue;
}
let id = if let Ok(_f) = Friend::get_id(&chat_db, &fgid) {
//let msg = handle_nmsg(m, is_me, gid, &base, &chat_db, f.id, eid, results)?;
//results.rpcs.push(chat_rpc::message_create(gid, &msg));
-1
} else {
-1
};
(eid, MESSAGE_TABLE_PATH, id)
}
SyncEvent::None => {
continue;
}
};
let account_db = group.account_db()?;
let account = group.account_mut(&gid)?;
let (merge_height, next_height, next_eid) = InnerEvent::merge_event(
&consensus_db,
&addr,
results,
account.own_height,
account.event,
height,
eid,
None,
)?;
account.update_consensus(&account_db, next_height, next_eid)?;
account_db.close()?;
OldEvent::merge(&consensus_db, eid, path, id, merge_height)?;
}
consensus_db.close()?;
Ok(())
}
}