From f6ee8bcf8331ad112263d1b54dad539f6e738637 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 17:17:02 -0700 Subject: [PATCH 01/22] merge friends --- daemon/crypto/constants.hpp | 2 + daemon/db/db.rs | 892 +++++++++++------- daemon/db/schema.rs | 65 +- daemon/db/tests.rs | 47 +- daemon/identifier/identifier.hpp | 1 - .../up.sql | 63 +- daemon/rpc/daemon_rpc.cc | 355 +++---- daemon/rpc/daemon_rpc.hpp | 12 - daemon/transmitter/transmitter.cc | 4 +- daemon/transmitter/transmitter.hpp | 12 +- 10 files changed, 848 insertions(+), 605 deletions(-) diff --git a/daemon/crypto/constants.hpp b/daemon/crypto/constants.hpp index 151777dd..1348ee36 100644 --- a/daemon/crypto/constants.hpp +++ b/daemon/crypto/constants.hpp @@ -35,6 +35,8 @@ constexpr size_t MAX_FRIENDS = MESSAGE_SIZE / ENCRYPTED_ACKING_BYTES; constexpr size_t MAX_ASYNC_FRIEND_REQUESTS = 500; constexpr size_t ASYNC_FRIEND_REQUEST_BATCH_SIZE = 1000; +// TODO: figure out a reasonable limit here... +constexpr size_t INVITATION_MESSAGE_MAX_PLAINTEXT_SIZE = 500; // NOTE: whenever these default values are changed, please make a database // migration in the shape of UPDATE config SET value = 'new_value' WHERE value = diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 3fa29ade..da6fd7e5 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -1,4 +1,4 @@ -use diesel::{deserialize::FromSql, prelude::*}; +use diesel::prelude::*; use std::{error::Error, fmt}; @@ -51,7 +51,9 @@ impl fmt::Display for DbError { } // keep this in sync with message.proto -pub const CONTROL_MESSAGE_OUTGOING_FRIEND_REQUEST: i32 = 0; +pub enum SystemMessage { + OutgoingInvitation = 0, +} // TODO(arvid): manage a connection pool for the DB here? // i.e. pool: Box or something @@ -112,7 +114,7 @@ where } } -impl Queryable for ffi::FriendRequestProgress +impl Queryable for ffi::InvitationProgress where DB: diesel::backend::Backend, i32: diesel::deserialize::FromSql, @@ -120,10 +122,9 @@ where type Row = i32; fn build(s: i32) -> diesel::deserialize::Result { match s { - 0 => Ok(ffi::FriendRequestProgress::Incoming), - 1 => Ok(ffi::FriendRequestProgress::OutgoingAsync), - 2 => Ok(ffi::FriendRequestProgress::OutgoingSync), - 3 => Ok(ffi::FriendRequestProgress::Complete), + 0 => Ok(ffi::InvitationProgress::OutgoingAsync), + 1 => Ok(ffi::InvitationProgress::OutgoingSync), + 2 => Ok(ffi::InvitationProgress::Complete), _ => Err("invalid friend request progress".into()), } } @@ -131,7 +132,7 @@ where use diesel::serialize::Output; use diesel::serialize::ToSql; -impl ToSql for ffi::FriendRequestProgress +impl ToSql for ffi::InvitationProgress where i32: ToSql, { @@ -139,12 +140,10 @@ where &'b self, out: &mut Output<'b, '_, diesel::sqlite::Sqlite>, ) -> diesel::serialize::Result { - // out.set_value(*self as i32); match *self { - ffi::FriendRequestProgress::Incoming => out.set_value(0 as i32), - ffi::FriendRequestProgress::OutgoingAsync => out.set_value(1 as i32), - ffi::FriendRequestProgress::OutgoingSync => out.set_value(2 as i32), - ffi::FriendRequestProgress::Complete => out.set_value(3 as i32), + ffi::InvitationProgress::OutgoingAsync => out.set_value(0 as i32), + ffi::InvitationProgress::OutgoingSync => out.set_value(1 as i32), + ffi::InvitationProgress::Complete => out.set_value(2 as i32), _ => return Err("invalid friend request progress".into()), } Ok(diesel::serialize::IsNull::No) @@ -157,7 +156,6 @@ where /// #[cxx::bridge(namespace = "db")] pub mod ffi { - // // WARNING: Diesel checks types. That's awesome. But IT DOES NOT CHECK THE ORDER. // so we need to make sure that the order here matches the order in schema.rs. @@ -171,8 +169,7 @@ pub mod ffi { // THIS SHOULD BE SAME AS THE SQL VERSIONS ABOVE #[derive(Debug, AsExpression)] #[diesel(sql_type = diesel::sql_types::Integer)] - enum FriendRequestProgress { - Incoming, + enum InvitationProgress { OutgoingAsync, OutgoingSync, Complete, @@ -182,50 +179,71 @@ pub mod ffi { pub uid: i32, pub unique_name: String, pub display_name: String, - pub public_id: String, - #[diesel(deserialize_as = FriendRequestProgress)] - pub request_progress: FriendRequestProgress, + #[diesel(deserialize_as = InvitationProgress)] + pub invitation_progress: InvitationProgress, pub deleted: bool, } + #[derive(Queryable)] + struct CompleteFriend { + pub uid: i32, + pub unique_name: String, + pub display_name: String, + #[diesel(deserialize_as = InvitationProgress)] + pub invitation_progress: InvitationProgress, + pub deleted: bool, + pub public_id: String, + pub completed_at: i64, // unix micros + } #[derive(Insertable)] #[diesel(table_name = crate::schema::friend)] struct FriendFragment { pub unique_name: String, pub display_name: String, - pub public_id: String, - pub request_progress: FriendRequestProgress, + pub invitation_progress: InvitationProgress, pub deleted: bool, } - #[derive(Queryable, Insertable)] - #[diesel(table_name = crate::schema::address)] + #[derive(Queryable)] struct Address { pub uid: i32, - pub friend_request_public_key: Vec, - pub friend_request_message: String, - pub kx_public_key: Vec, pub read_index: i32, - pub ack_index: i32, pub read_key: Vec, pub write_key: Vec, + pub ack_index: i32, } - struct AddAddress { - pub unique_name: String, - pub friend_request_public_key: Vec, - pub friend_request_message: String, - pub kx_public_key: Vec, + #[derive(Insertable)] + #[diesel(table_name = crate::schema::transmission)] + struct AddressFragment { pub read_index: i32, pub read_key: Vec, pub write_key: Vec, + pub ack_index: i32, } - - #[derive(Queryable, Insertable)] - #[diesel(table_name = crate::schema::status)] - struct Status { - pub uid: i32, - pub sent_acked_seqnum: i32, - pub received_seqnum: i32, + #[derive(Queryable)] + struct OutgoingSyncInvitation { + pub friend_uid: i32, + pub unique_name: String, + pub display_name: String, + pub invitation_progress: InvitationProgress, + pub story: String, + pub sent_at: i64, // unix micros + } + #[derive(Queryable)] + struct OutgoingAsyncInvitation { + pub friend_uid: i32, + pub unique_name: String, + pub display_name: String, + pub invitation_progress: InvitationProgress, + pub public_id: String, + pub message: String, + pub sent_at: i64, // unix micros + } + #[derive(Queryable)] + struct IncomingInvitation { + pub public_id: String, + pub message: String, + pub received_at: i64, // unix micros } #[derive(Queryable)] @@ -414,43 +432,73 @@ pub mod ffi { // Friends // fn get_friend(&self, unique_name: &str) -> Result; - fn get_friends(&self) -> Result>; - // the next method returns every entry in the friend db, including pending, accepted, and deleted friends. - fn get_friends_all_status(&self) -> Result>; - // fails if a friend with unique_name already exists - fn create_friend( - &self, - unique_name: &str, - display_name: &str, - public_key: &str, - max_friends: i32, - ) -> Result; - // adds a friend address and also makes the friend enabled - fn add_friend_address(&self, add_address: AddAddress, max_friends: i32) -> Result<()>; + // returns all friends that are complete and !deleted + fn get_friends(&self) -> Result>; + fn get_friends_including_outgoing(&self) -> Result>; + // deletes a friend if it exists fn delete_friend(&self, unique_name: &str) -> Result<()>; - // returns address iff enabled && !deleted + // returns address iff entry in transmission table exists fn get_friend_address(&self, uid: i32) -> Result
; // fails if no such friend exists fn get_random_enabled_friend_address_excluding(&self, uids: Vec) -> Result
; // - // Async Friend Requests + // Invitations // - fn has_space_for_async_friend_requests(&self) -> Result; - fn add_outgoing_async_friend_requests( + // currently we only support 1 async invitation at a time. + fn has_space_for_async_invitations(&self) -> Result; + // fails if a friend with unique_name already exists, or there are too many friends in the DB + fn add_outgoing_sync_invitation( &self, - friend_struct: FriendFragment, - address_struct: AddAddress, - ) -> Result<()>; - fn get_outgoing_async_friend_requests(&self) -> Result>; - fn add_incoming_async_friend_requests( + unique_name: &str, + display_name: &str, + story: &str, + kx_public_key: Vec, + read_index: i32, + read_key: Vec, + write_key: Vec, + max_friends: i32, // this is the constant defined in constants.hpp. TODO(sualeh): move it to the ffi. + ) -> Result; + // fails if a friend with unique_name already exists, or there are too many friends in the DB + // also fails if there is already an outgoing async invitation. we only support one at a time! TODO: fix this. + fn add_outgoing_async_invitation( + &self, + unique_name: &str, + display_name: &str, + public_id: &str, + friend_request_public_key: Vec, + kx_public_key: Vec, + message: &str, + read_index: i32, + read_key: Vec, + write_key: Vec, + max_friends: i32, // this is the constant defined in constants.hpp. TODO(sualeh): move it to the ffi. + ) -> Result; + // if an invitation with the same public_id already exists, we replace the message + // if an outgoing invitation with the same public_id already exists, we automatically make them become friends + // if a friend with the same public_id already exists or is deleted, we ignore + fn add_incoming_async_invitation(&self, public_id: &str, message: &str) -> Result<()>; + // get invitations + fn get_outgoing_sync_invitations(&self) -> Result>; + fn get_outgoing_async_invitations(&self) -> Result>; + fn get_incoming_invitations(&self) -> Result>; + // accepts the incoming invitation with the given public_id + // fails if too many friends (invitation stays in the database) + // TODO(sualeh): move the max_friends to the ffi + fn accept_incoming_invitation( &self, - friend_struct: FriendFragment, - address_struct: AddAddress, + public_id: &str, + unique_name: &str, + display_name: &str, + friend_request_public_key: Vec, + kx_public_key: Vec, + read_index: i32, + read_key: Vec, + write_key: Vec, + max_friends: i32, ) -> Result<()>; - fn get_incoming_async_friend_requests(&self) -> Result>; - fn approve_async_friend_request(&self, unique_name: &str, max_friends: i32) -> Result<()>; - fn deny_async_friend_request(&self, unique_name: &str) -> Result<()>; + // simply deletes the incoming invitation + fn deny_incoming_invitation(&self, public_id: &str) -> Result<()>; // // Messages @@ -463,8 +511,8 @@ pub mod ffi { chunk: IncomingChunkFragment, num_chunks: i32, ) -> Result; - // receive the control message telling us to add the friend - fn receive_friend_request_control_message( + // receive the system message telling us to add the friend + fn receive_invitation_system_message( &self, from_friend: i32, sequence_number: i32, @@ -731,14 +779,26 @@ impl DB { } } - pub fn get_friends(&self) -> Result, DbError> { + pub fn get_friends(&self) -> Result, DbError> { let mut conn = self.connect()?; + use crate::schema::complete_friend; use crate::schema::friend; + // only get complete friends if let Ok(v) = friend::table - .filter(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) + .filter(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) .filter(friend::deleted.eq(false)) - .load::(&mut conn) + .inner_join(complete_friend::table) + .select(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + friend::deleted, + complete_friend::public_id, + complete_friend::completed_at, + )) + .load::(&mut conn) { Ok(v) } else { @@ -746,121 +806,27 @@ impl DB { } } - pub fn get_friends_all_status(&self) -> Result, DbError> { + pub fn get_friends_including_outgoing(&self) -> Result, DbError> { let mut conn = self.connect()?; use crate::schema::friend; - if let Ok(v) = friend::table.filter(friend::deleted.eq(false)).load::(&mut conn) { + if let Ok(v) = friend::table + .filter(friend::deleted.eq(false)) + .select(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + friend::deleted, + )) + .load::(&mut conn) + { Ok(v) } else { Err(DbError::Unknown("failed to get friends".to_string())) } } - pub fn create_friend( - &self, - unique_name: &str, - display_name: &str, - public_id: &str, - max_friends: i32, - ) -> Result { - let mut conn = self.connect()?; - use crate::schema::friend; - - let f = ffi::FriendFragment { - unique_name: unique_name.to_string(), - display_name: display_name.to_string(), - public_id: public_id.to_string(), - request_progress: ffi::FriendRequestProgress::Complete, - deleted: false, - }; - let r = conn.transaction(|conn_b| { - // check if a friend with this name already exists - let count = friend::table - .filter(friend::unique_name.eq(unique_name)) - .count() - .get_result::(conn_b)?; - if count > 0 { - return Err(diesel::result::Error::RollbackTransaction); - } - let count = friend::table.count().get_result::(conn_b)?; - if count >= max_friends.into() { - return Err(diesel::result::Error::RollbackTransaction); - } - - diesel::insert_into(friend::table).values(&f).get_result::(conn_b) - }); - - r.map_err(|e| match e { - diesel::result::Error::RollbackTransaction => { - DbError::AlreadyExists("friend already exists, or too many friends".to_string()) - } - _ => DbError::Unknown(format!("failed to insert friend: {}", e)), - }) - } - - pub fn add_friend_address( - &self, - add_address: ffi::AddAddress, - max_friends: i32, - ) -> Result<(), DbError> { - let mut conn = self.connect()?; - use crate::schema::address; - use crate::schema::friend; - use crate::schema::status; - - // transaction because we need to pick a new ack_index - conn - .transaction(|conn_b| { - let uid = friend::table - .filter(friend::unique_name.eq(add_address.unique_name)) - .select(friend::uid) - .first::(conn_b)?; - - let ack_indices = address::table - .inner_join(friend::table) - .filter(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) - .filter(friend::deleted.eq(false)) - .select(address::ack_index) - .load::(conn_b)?; - let mut possible_ack_indices = Vec::::new(); - for i in 0..max_friends { - if !ack_indices.contains(&i) { - possible_ack_indices.push(i); - } - } - use rand::seq::SliceRandom; - let ack_index_opt = possible_ack_indices.choose(&mut rand::thread_rng()); - let ack_index = ack_index_opt.ok_or(diesel::result::Error::RollbackTransaction)?; - let address = ffi::Address { - uid, - read_index: add_address.read_index, - friend_request_message: add_address.friend_request_message, - friend_request_public_key: add_address.friend_request_public_key, - kx_public_key: add_address.kx_public_key, - ack_index: *ack_index, - read_key: add_address.read_key, - write_key: add_address.write_key, - }; - diesel::insert_into(address::table).values(&address).execute(conn_b)?; - - diesel::update(friend::table.find(uid)) - .set(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) - .execute(conn_b)?; - - let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; - diesel::insert_into(status::table).values(&status).execute(conn_b)?; - - Ok(()) - }) - .map_err(|e| match e { - diesel::result::Error::RollbackTransaction => { - DbError::AlreadyExists("no free ack index".to_string()) - } - _ => DbError::Unknown(format!("failed to insert address: {}", e)), - }) - } - pub fn delete_friend(&self, unique_name: &str) -> Result<(), DbError> { let mut conn = self.connect()?; use crate::schema::friend; @@ -946,10 +912,10 @@ impl DB { .select(outgoing_chunk::control_message) .load::(conn_b)?; for control_message in control_chunks { - if control_message == CONTROL_MESSAGE_OUTGOING_FRIEND_REQUEST { + if control_message == SystemMessage::OutgoingInvitation { // yay! they shall now be considered a Real Friend diesel::update(friend::table.find(uid)) - .set(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) + .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) .execute(conn_b)?; } } @@ -1131,7 +1097,7 @@ impl DB { } } - fn receive_friend_request_control_message( + fn receive_invitation_system_message( &self, from_friend: i32, sequence_number: i32, @@ -1145,8 +1111,10 @@ impl DB { return Ok(chunk_status); } // move the friend to become an actual friend + // TODO: the system message should contain their public key, and we should add it and verify it! + // TODO: we need to create the right kind of table record here!!! and delete the other table record diesel::update(friend::table.find(from_friend)) - .set(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) + .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) .execute(conn_b)?; Ok(ffi::ReceiveChunkStatus::NewChunk) @@ -1229,7 +1197,6 @@ impl DB { use crate::schema::friend; use crate::schema::status; let wide_friends = friend::table - .filter(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) .filter(friend::deleted.eq(false)) .inner_join(status::table) .inner_join(address::table); @@ -1266,9 +1233,8 @@ impl DB { use crate::schema::friend; // get a random friend that is not deleted excluding the ones in the uids list - let q = friend::table - .filter(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) - .filter(friend::deleted.eq(false)); + // we alow getting any outgoing friend too. they should be treated normally + let q = friend::table.filter(friend::deleted.eq(false)); // Inner join to get the (friend, address) pairs and then select the address. let q = q.inner_join(address::table).select(address::all_columns); @@ -1310,12 +1276,11 @@ impl DB { message: &str, chunks: Vec, ) -> Result<(), DbError> { - // TODO: implement this // We chunk in C++ because we potentially need to do things with protobuf // What do we do here? // 1. Create a new message. // 2. Create a new sent message. - // 1. Take in all chunks, create outgoing_chunks. + // 3. Take in all chunks, create outgoing_chunks. let mut conn = self.connect()?; use crate::schema::message; use crate::schema::outgoing_chunk; @@ -1630,13 +1595,15 @@ impl DB { //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// //||| ||| - //||| ASYNC FRIEND REQUEST METHODS ||| + //||| INVITATION METHODS ||| //||| ||| //////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////// - pub fn has_space_for_async_friend_requests(&self) -> Result { + + pub fn has_space_for_async_invitations(&self) -> Result { // return true iff no async friend requests are in the database - if let Ok(f) = self.get_outgoing_async_friend_requests() { + // TODO: update the limit to allow more outgoing async requests + if let Ok(f) = self.get_outgoing_async_invitations() { if f.len() == 0 { return Ok(true); } else { @@ -1647,192 +1614,387 @@ impl DB { } } - pub fn add_outgoing_async_friend_requests( + pub fn add_outgoing_sync_invitation( &self, - friend_struct: ffi::FriendFragment, - address_struct: ffi::AddAddress, - ) -> Result<(), DbError> { - if friend_struct.request_progress != ffi::FriendRequestProgress::OutgoingAsync { - return Err(DbError::InvalidArgument("not an outgoing async request".to_string())); - } - let mut conn = self.connect().unwrap(); - use crate::schema::address; - use crate::schema::friend; - use crate::schema::status; - - let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { - // IMPORTANT TODO: what if the friend already exists, but has been deleted? - // We can either recycle the old entry, or create a new entry. - // We choose the latter, which also erases the message history. - // insert friend and address into database - let friend = diesel::insert_into(friend::table) - .values(friend_struct) - .get_result::(conn_b)?; - let uid = friend.uid; - // we add the address and the status to their tables - let address = ffi::Address { - uid, - read_index: address_struct.read_index, - friend_request_message: address_struct.friend_request_message, - friend_request_public_key: address_struct.friend_request_public_key, - kx_public_key: address_struct.kx_public_key, - ack_index: -1, // we don't allocate the ack index yet - read_key: address_struct.read_key, - write_key: address_struct.write_key, - }; - diesel::insert_into(address::table).values(&address).execute(conn_b)?; - let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; - diesel::insert_into(status::table).values(&status).execute(conn_b)?; - Ok(()) - }); - match r { - Ok(_) => Ok(()), - Err(e) => Err(DbError::Unknown(format!("add_outgoing_async_friend_requests: {}", e))), - } - } - - pub fn get_outgoing_async_friend_requests(&self) -> Result, DbError> { - let mut conn = self.connect()?; // if error then crash function - use crate::schema::friend; - - if let Ok(f) = friend::table - .filter(friend::deleted.eq(false)) - .filter(friend::request_progress.eq(ffi::FriendRequestProgress::OutgoingAsync)) - .load::(&mut conn) - { - Ok(f) - } else { - Err(DbError::NotFound("failed to get friend".to_string())) - } - } - - pub fn add_incoming_async_friend_requests( + unique_name: &str, + display_name: &str, + story: &str, + kx_public_key: Vec, + read_index: i32, + read_key: Vec, + write_key: Vec, + max_friends: i32, + ) -> Result { + return Err(DbError::Unimplemented( + "add_outgoing_sync_invitation CHECK BELOW IMPL FOR NEW INERFACE".to_string(), + )); + // let mut conn = self.connect()?; + // use crate::schema::friend; + + // let f = ffi::FriendFragment { + // unique_name: unique_name.to_string(), + // display_name: display_name.to_string(), + // public_id: public_id.to_string(), + // request_progress: ffi::InvitationProgress::Complete, + // deleted: false, + // }; + // let r = conn.transaction(|conn_b| { + // // check if a friend with this name already exists + // let count = friend::table + // .filter(friend::unique_name.eq(unique_name)) + // .count() + // .get_result::(conn_b)?; + // if count > 0 { + // return Err(diesel::result::Error::RollbackTransaction); + // } + // let count = friend::table.count().get_result::(conn_b)?; + // if count >= max_friends.into() { + // return Err(diesel::result::Error::RollbackTransaction); + // } + + // diesel::insert_into(friend::table).values(&f).get_result::(conn_b) + // }); + + // r.map_err(|e| match e { + // diesel::result::Error::RollbackTransaction => { + // DbError::AlreadyExists("friend already exists, or too many friends".to_string()) + // } + // _ => DbError::Unknown(format!("failed to insert friend: {}", e)), + // }) + } + + pub fn add_outgoing_async_invitation( &self, - friend_struct: ffi::FriendFragment, - address_struct: ffi::AddAddress, + unique_name: &str, + display_name: &str, + public_id: &str, + friend_request_public_key: Vec, + kx_public_key: Vec, + message: &str, + read_index: i32, + read_key: Vec, + write_key: Vec, + max_friends: i32, + ) -> Result { + return Err(DbError::Unimplemented( + "add_outgoing_async_invitation CHECK BELOW IMPL FOR NEW INERFACE".to_string(), + )); + // TODO: check that we have space for async invitation + + // let mut conn = self.connect().unwrap(); + // use crate::schema::address; + // use crate::schema::friend; + // use crate::schema::status; + + // // TODO: check if we already have outgoing async friend request, and if so, we don't support adding this right now + // // (we can currently only do one async friend request at a time) + + // let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + // // IMPORTANT TODO: what if the friend already exists, but has been deleted? + // // We can either recycle the old entry, or create a new entry. + // // We choose the latter, which also erases the message history. + // // insert friend and address into database + // let friend = diesel::insert_into(friend::table) + // .values(friend_struct) + // .get_result::(conn_b)?; + // let uid = friend.uid; + // // we add the address and the status to their tables + // let address = ffi::Address { + // uid, + // read_index: address_struct.read_index, + // friend_request_message: address_struct.friend_request_message, + // friend_request_public_key: address_struct.friend_request_public_key, + // kx_public_key: address_struct.kx_public_key, + // ack_index: -1, // we don't allocate the ack index yet + // read_key: address_struct.read_key, + // write_key: address_struct.write_key, + // }; + // diesel::insert_into(address::table).values(&address).execute(conn_b)?; + // let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; + // diesel::insert_into(status::table).values(&status).execute(conn_b)?; + // Ok(()) + // }); + // match r { + // Ok(_) => Ok(()), + // Err(e) => Err(DbError::Unknown(format!("add_outgoing_async_friend_requests: {}", e))), + // } + } + + pub fn add_incoming_async_invitation( + &self, + public_id: &str, + message: &str, ) -> Result<(), DbError> { - if friend_struct.request_progress != ffi::FriendRequestProgress::Incoming { - return Err(DbError::InvalidArgument("not an incoming request".to_string())); - } - let mut conn = self.connect().unwrap(); - use crate::schema::address; - use crate::schema::friend; - use crate::schema::status; - - let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { - // IMPORTANT TODO: what if the friend already exists? - // We can either recycle the old entry, or create a new entry. - // We choose the latter, which also erases the message history. - // insert friend and address into database - let friend = diesel::insert_into(friend::table) - .values(friend_struct) - .get_result::(conn_b)?; - let uid = friend.uid; - // we add the address and the status to their tables - let address = ffi::Address { - uid, - read_index: address_struct.read_index, - friend_request_message: address_struct.friend_request_message, - friend_request_public_key: address_struct.friend_request_public_key, - kx_public_key: address_struct.kx_public_key, - ack_index: -1, // we don't allocate the ack index yet - read_key: address_struct.read_key, - write_key: address_struct.write_key, - }; - diesel::insert_into(address::table).values(&address).execute(conn_b)?; - let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; - diesel::insert_into(status::table).values(&status).execute(conn_b)?; + return Err(DbError::Unimplemented( + "add_incoming_async_invitation CHECK BELOW IMPL FOR NEW INERFACE".to_string(), + )); - Ok(()) - }); - match r { - Ok(_) => Ok(()), - Err(e) => Err(DbError::Unknown(format!("add_incoming_async_friend_requests: {}", e))), - } + // let mut conn = self.connect().unwrap(); + // use crate::schema::address; + // use crate::schema::friend; + // use crate::schema::status; + + // let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + // // IMPORTANT TODO: what if the friend already exists? + // // We can either recycle the old entry, or create a new entry. + // // We choose the latter, which also erases the message history. + // // insert friend and address into database + // let friend = diesel::insert_into(friend::table) + // .values(friend_struct) + // .get_result::(conn_b)?; + // let uid = friend.uid; + // // we add the address and the status to their tables + // let address = ffi::Address { + // uid, + // read_index: address_struct.read_index, + // friend_request_message: address_struct.friend_request_message, + // friend_request_public_key: address_struct.friend_request_public_key, + // kx_public_key: address_struct.kx_public_key, + // ack_index: -1, // we don't allocate the ack index yet + // read_key: address_struct.read_key, + // write_key: address_struct.write_key, + // }; + // diesel::insert_into(address::table).values(&address).execute(conn_b)?; + // let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; + // diesel::insert_into(status::table).values(&status).execute(conn_b)?; + + // Ok(()) + // }); + // match r { + // Ok(_) => Ok(()), + // Err(e) => Err(DbError::Unknown(format!("add_incoming_async_friend_requests: {}", e))), + // } + } + + pub fn get_outgoing_sync_invitations(&self) -> Result, DbError> { + return Err(DbError::Unimplemented( + "get_outgoing_sync_invitations CHECK BELOW IMPL FOR NEW INERFACE".to_string(), + )); } + pub fn get_outgoing_async_invitations( + &self, + ) -> Result, DbError> { + return Err(DbError::Unimplemented( + "get_outgoing_async_invitations CHECK BELOW IMPL FOR NEW INERFACE".to_string(), + )); - pub fn get_incoming_async_friend_requests(&self) -> Result, DbError> { - let mut conn = self.connect()?; // if error then crash function - use crate::schema::friend; + // let mut conn = self.connect()?; // if error then crash function + // use crate::schema::friend; + + // if let Ok(f) = friend::table + // .filter(friend::deleted.eq(false)) + // .filter(friend::request_progress.eq(ffi::InvitationProgress::OutgoingAsync)) + // .load::(&mut conn) + // { + // Ok(f) + // } else { + // Err(DbError::NotFound("failed to get friend".to_string())) + // } + } + pub fn get_incoming_invitations(&self) -> Result, DbError> { + return Err(DbError::Unimplemented( + "get_incoming_invitations CHECK BELOW IMPL FOR NEW INERFACE".to_string(), + )); + // let mut conn = self.connect()?; // if error then crash function + // use crate::schema::friend; - if let Ok(f) = friend::table - .filter(friend::deleted.eq(false)) - .filter(friend::request_progress.eq(ffi::FriendRequestProgress::Incoming)) - .load::(&mut conn) - { - Ok(f) - } else { - Err(DbError::NotFound("failed to get friend".to_string())) - } + // if let Ok(f) = friend::table + // .filter(friend::deleted.eq(false)) + // .filter(friend::request_progress.eq(ffi::InvitationProgress::Incoming)) + // .load::(&mut conn) + // { + // Ok(f) + // } else { + // Err(DbError::NotFound("failed to get friend".to_string())) + // } } - pub fn approve_async_friend_request( + pub fn accept_incoming_invitation( &self, + public_id: &str, unique_name: &str, + display_name: &str, + friend_request_public_key: Vec, + kx_public_key: Vec, + read_index: i32, + read_key: Vec, + write_key: Vec, max_friends: i32, ) -> Result<(), DbError> { - // This function is called when the user accepts a friend request. - let mut conn = self.connect().unwrap(); - - use crate::schema::address; - use crate::schema::friend; - - // we change the progress field to ACTUAL_FRIEND, meaning that the friend is approved - conn - .transaction::<_, diesel::result::Error, _>(|conn_b| { - // update the progress field of friend - diesel::update(friend::table.filter(friend::unique_name.eq(unique_name))) - .set(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) - .execute(conn_b)?; - // we need to get the uid of the friend to update the address - let uid = friend::table - .filter(friend::unique_name.eq(unique_name)) - .select(friend::uid) - .first::(conn_b)?; - // another important thing is that we need to allocate ack_index for the address - // we do this by updating the address table - let ack_indices = address::table - .inner_join(friend::table) - .filter(friend::request_progress.eq(ffi::FriendRequestProgress::Complete)) - .filter(friend::deleted.eq(false)) - .select(address::ack_index) - .load::(conn_b)?; - let mut possible_ack_indices = Vec::::new(); - for i in 0..max_friends { - if !ack_indices.contains(&i) { - possible_ack_indices.push(i); - } - } - use rand::seq::SliceRandom; - let ack_index_opt = possible_ack_indices.choose(&mut rand::thread_rng()); - let ack_index = ack_index_opt.ok_or(diesel::result::Error::RollbackTransaction)?; - diesel::update(address::table) - .filter(address::uid.eq(uid)) - .set(address::ack_index.eq(ack_index)) - .execute(conn_b)?; - Ok(()) - }) - .map_err(|e| match e { - diesel::result::Error::RollbackTransaction => { - DbError::AlreadyExists("no free ack index".to_string()) - } - _ => DbError::Unknown(format!("failed to insert address: {}", e)), - }) - } - - pub fn deny_async_friend_request(&self, unique_name: &str) -> Result<(), DbError> { - // This function is called when the user rejects a friend request. - let mut conn = self.connect().unwrap(); - use crate::schema::friend; - - // we change the deleted flag to true, meaning that the friend is deleted - if let Ok(_) = diesel::update(friend::table.filter(friend::unique_name.eq(unique_name))) - .set(friend::deleted.eq(true)) - .execute(&mut conn) - { - Ok(()) - } else { - Err(DbError::Unknown("deny_async_friend_request FAILED".to_string())) - } - } + return Err(DbError::Unimplemented( + "accept_incoming_invitation CHECK BELOW IMPL FOR NEW INTERFACE".to_string(), + )); + // // This function is called when the user accepts a friend request. + // let mut conn = self.connect().unwrap(); + + // use crate::schema::address; + // use crate::schema::friend; + + // // we change the progress field to ACTUAL_FRIEND, meaning that the friend is approved + // conn + // .transaction::<_, diesel::result::Error, _>(|conn_b| { + // // update the progress field of friend + // diesel::update(friend::table.filter(friend::unique_name.eq(unique_name))) + // .set(friend::request_progress.eq(ffi::InvitationProgress::Complete)) + // .execute(conn_b)?; + // // we need to get the uid of the friend to update the address + // let uid = friend::table + // .filter(friend::unique_name.eq(unique_name)) + // .select(friend::uid) + // .first::(conn_b)?; + // // another important thing is that we need to allocate ack_index for the address + // // we do this by updating the address table + // let ack_indices = address::table + // .inner_join(friend::table) + // .filter(friend::request_progress.eq(ffi::InvitationProgress::Complete)) + // .filter(friend::deleted.eq(false)) + // .select(address::ack_index) + // .load::(conn_b)?; + // let mut possible_ack_indices = Vec::::new(); + // for i in 0..max_friends { + // if !ack_indices.contains(&i) { + // possible_ack_indices.push(i); + // } + // } + // use rand::seq::SliceRandom; + // let ack_index_opt = possible_ack_indices.choose(&mut rand::thread_rng()); + // let ack_index = ack_index_opt.ok_or(diesel::result::Error::RollbackTransaction)?; + // diesel::update(address::table) + // .filter(address::uid.eq(uid)) + // .set(address::ack_index.eq(ack_index)) + // .execute(conn_b)?; + // Ok(()) + // }) + // .map_err(|e| match e { + // diesel::result::Error::RollbackTransaction => { + // DbError::AlreadyExists("no free ack index".to_string()) + // } + // _ => DbError::Unknown(format!("failed to insert address: {}", e)), + // }) + } + + pub fn deny_incoming_invitation(&self, unique_name: &str) -> Result<(), DbError> { + return Err(DbError::Unimplemented( + "deny_incoming_invitation CHECK BELOW IMPL FOR NEW INTERFACE".to_string(), + )); + // // This function is called when the user rejects a friend request. + // let mut conn = self.connect().unwrap(); + // use crate::schema::friend; + + // // we change the deleted flag to true, meaning that the friend is deleted + // if let Ok(_) = diesel::update(friend::table.filter(friend::unique_name.eq(unique_name))) + // .set(friend::deleted.eq(true)) + // .execute(&mut conn) + // { + // Ok(()) + // } else { + // Err(DbError::Unknown("deny_async_friend_request FAILED".to_string())) + // } + } + + // Inspiration code + // TODO: remove + // pub fn create_friend( + // &self, + // unique_name: &str, + // display_name: &str, + // public_id: &str, + // max_friends: i32, + // ) -> Result { + // let mut conn = self.connect()?; + // use crate::schema::friend; + + // let f = ffi::FriendFragment { + // unique_name: unique_name.to_string(), + // display_name: display_name.to_string(), + // public_id: public_id.to_string(), + // request_progress: ffi::InvitationProgress::Complete, + // deleted: false, + // }; + // let r = conn.transaction(|conn_b| { + // // check if a friend with this name already exists + // let count = friend::table + // .filter(friend::unique_name.eq(unique_name)) + // .count() + // .get_result::(conn_b)?; + // if count > 0 { + // return Err(diesel::result::Error::RollbackTransaction); + // } + // let count = friend::table.count().get_result::(conn_b)?; + // if count >= max_friends.into() { + // return Err(diesel::result::Error::RollbackTransaction); + // } + + // diesel::insert_into(friend::table).values(&f).get_result::(conn_b) + // }); + + // r.map_err(|e| match e { + // diesel::result::Error::RollbackTransaction => { + // DbError::AlreadyExists("friend already exists, or too many friends".to_string()) + // } + // _ => DbError::Unknown(format!("failed to insert friend: {}", e)), + // }) + // } + + // pub fn add_friend_address( + // &self, + // add_address: ffi::AddAddress, + // max_friends: i32, + // ) -> Result<(), DbError> { + // let mut conn = self.connect()?; + // use crate::schema::address; + // use crate::schema::friend; + // use crate::schema::status; + + // // transaction because we need to pick a new ack_index + // conn + // .transaction(|conn_b| { + // let uid = friend::table + // .filter(friend::unique_name.eq(add_address.unique_name)) + // .select(friend::uid) + // .first::(conn_b)?; + + // let ack_indices = address::table + // .inner_join(friend::table) + // .filter(friend::request_progress.eq(ffi::InvitationProgress::Complete)) + // .filter(friend::deleted.eq(false)) + // .select(address::ack_index) + // .load::(conn_b)?; + // let mut possible_ack_indices = Vec::::new(); + // for i in 0..max_friends { + // if !ack_indices.contains(&i) { + // possible_ack_indices.push(i); + // } + // } + // use rand::seq::SliceRandom; + // let ack_index_opt = possible_ack_indices.choose(&mut rand::thread_rng()); + // let ack_index = ack_index_opt.ok_or(diesel::result::Error::RollbackTransaction)?; + // let address = ffi::Address { + // uid, + // read_index: add_address.read_index, + // friend_request_message: add_address.friend_request_message, + // friend_request_public_key: add_address.friend_request_public_key, + // kx_public_key: add_address.kx_public_key, + // ack_index: *ack_index, + // read_key: add_address.read_key, + // write_key: add_address.write_key, + // }; + // diesel::insert_into(address::table).values(&address).execute(conn_b)?; + + // diesel::update(friend::table.find(uid)) + // .set(friend::request_progress.eq(ffi::InvitationProgress::Complete)) + // .execute(conn_b)?; + + // let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; + // diesel::insert_into(status::table).values(&status).execute(conn_b)?; + + // Ok(()) + // }) + // .map_err(|e| match e { + // diesel::result::Error::RollbackTransaction => { + // DbError::AlreadyExists("no free ack index".to_string()) + // } + // _ => DbError::Unknown(format!("failed to insert address: {}", e)), + // }) + // } } diff --git a/daemon/db/schema.rs b/daemon/db/schema.rs index 76e26e9b..f0ec0af9 100644 --- a/daemon/db/schema.rs +++ b/daemon/db/schema.rs @@ -1,15 +1,12 @@ // @generated automatically by Diesel CLI. diesel::table! { - address (uid) { - uid -> Integer, + complete_friend (friend_uid) { + friend_uid -> Integer, + public_id -> Text, friend_request_public_key -> Binary, - friend_request_message -> Text, kx_public_key -> Binary, - read_index -> Integer, - ack_index -> Integer, - read_key -> Binary, - write_key -> Binary, + completed_at -> BigInt, } } @@ -35,8 +32,7 @@ diesel::table! { uid -> Integer, unique_name -> Text, display_name -> Text, - public_id -> Text, - request_progress -> Integer, + invitation_progress -> Integer, deleted -> Bool, } } @@ -51,6 +47,15 @@ diesel::table! { } } +diesel::table! { + incoming_invitation (friend_uid) { + friend_uid -> Integer, + public_id -> Text, + message -> Text, + received_at -> BigInt, + } +} + diesel::table! { message (uid) { uid -> Integer, @@ -58,6 +63,17 @@ diesel::table! { } } +diesel::table! { + outgoing_async_invitation (friend_uid) { + friend_uid -> Integer, + public_id -> Text, + friend_request_public_key -> Binary, + kx_public_key -> Binary, + message -> Text, + sent_at -> BigInt, + } +} + diesel::table! { outgoing_chunk (to_friend, sequence_number) { to_friend -> Integer, @@ -70,6 +86,15 @@ diesel::table! { } } +diesel::table! { + outgoing_sync_invitation (friend_uid) { + friend_uid -> Integer, + story -> Text, + kx_public_key -> Binary, + sent_at -> BigInt, + } +} + diesel::table! { received (uid) { uid -> Integer, @@ -109,37 +134,47 @@ diesel::table! { } diesel::table! { - status (uid) { - uid -> Integer, + transmission (friend_uid) { + friend_uid -> Integer, + read_index -> Integer, + read_key -> Binary, + write_key -> Binary, + ack_index -> Nullable, sent_acked_seqnum -> Integer, received_seqnum -> Integer, } } -diesel::joinable!(address -> friend (uid)); +diesel::joinable!(complete_friend -> friend (friend_uid)); diesel::joinable!(config -> registration (registration_uid)); diesel::joinable!(draft -> friend (to_friend)); diesel::joinable!(draft -> message (uid)); diesel::joinable!(incoming_chunk -> friend (from_friend)); diesel::joinable!(incoming_chunk -> received (message_uid)); +diesel::joinable!(incoming_invitation -> friend (friend_uid)); +diesel::joinable!(outgoing_async_invitation -> friend (friend_uid)); diesel::joinable!(outgoing_chunk -> friend (to_friend)); diesel::joinable!(outgoing_chunk -> sent (message_uid)); +diesel::joinable!(outgoing_sync_invitation -> friend (friend_uid)); diesel::joinable!(received -> friend (from_friend)); diesel::joinable!(received -> message (uid)); diesel::joinable!(sent -> friend (to_friend)); diesel::joinable!(sent -> message (uid)); -diesel::joinable!(status -> friend (uid)); +diesel::joinable!(transmission -> friend (friend_uid)); diesel::allow_tables_to_appear_in_same_query!( - address, + complete_friend, config, draft, friend, incoming_chunk, + incoming_invitation, message, + outgoing_async_invitation, outgoing_chunk, + outgoing_sync_invitation, received, registration, sent, - status, + transmission, ); diff --git a/daemon/db/tests.rs b/daemon/db/tests.rs index ea7be99a..0ad3b78a 100644 --- a/daemon/db/tests.rs +++ b/daemon/db/tests.rs @@ -220,8 +220,8 @@ fn test_async_add_friend() { db.do_register(config_data).unwrap(); // check initial state - assert!(db.has_space_for_async_friend_requests().unwrap()); - assert!(db.get_incoming_async_friend_requests().unwrap().is_empty()); + assert!(db.has_space_for_async_invitations().unwrap()); + assert!(db.get_incoming_invitations().unwrap().is_empty()); // add an incoming friend request let friend_name = "friend_1"; let friend_request = ffi::FriendFragment { @@ -241,35 +241,38 @@ fn test_async_add_friend() { friend_request_public_key: br#"xxxx"#.to_vec(), friend_request_message: "finally made a friend".to_string(), }; - db.add_incoming_async_friend_requests(friend_request, address).unwrap(); - let friend_requests = db.get_incoming_async_friend_requests().unwrap(); + db.add_incoming_async_invitation("fake_public_id_string", "hi! do you want to be my friend?") + .unwrap(); + let friend_requests = db.get_incoming_invitations().unwrap(); // check that we have a friend request assert_eq!(friend_requests.len(), 1); - assert_eq!(friend_requests[0].unique_name, friend_name); - assert_eq!(friend_requests[0].public_id, "tttt"); - - // this uid now identifies the friend - let uid = friend_requests[0].uid; - let address = db.get_friend_address(uid).unwrap(); - // check the associated address & status struct - // the ack index shouldn't have been set yet - assert!(address.ack_index < 0); - assert!(address.uid == uid); + assert_eq!(friend_requests[0].public_id, "fake_public_id_string"); + assert_eq!(friend_requests[0].message, "hi! do you want to be my friend?"); // approve the friend request - let max_friend = 99; - db.approve_async_friend_request(friend_name, max_friend).unwrap(); + let max_friends = 20; + db.accept_incoming_invitation( + "fake_public_id_string", + friend_name, + "Display Name", + br#"xPubxxx"#.to_vec(), + br#"xKxxxx"#.to_vec(), + 0, + br#"rrrrrrrr"#.to_vec(), + br#"wwww"#.to_vec(), + max_friends, + ) + .unwrap(); // check that the friend request is gone - let friend_requests_new = db.get_incoming_async_friend_requests().unwrap(); + let friend_requests_new = db.get_incoming_invitations().unwrap(); assert_eq!(friend_requests_new.len(), 0); // check that we have a friend let friends = db.get_friends().unwrap(); assert_eq!(friends.len(), 1); - assert_eq!(friends[0].uid, uid); - assert_eq!(friends[0].public_id, "tttt"); - assert_eq!(friends[0].unique_name, "friend_1"); + assert_eq!(friends[0].public_id, "fake_public_id_string"); + assert_eq!(friends[0].unique_name, friend_name); // check the friend address - let new_address = db.get_friend_address(uid).unwrap(); - assert_eq!(new_address.uid, uid); + let new_address = db.get_friend_address(friends[0].uid).unwrap(); + assert_eq!(new_address.uid, friends[0].uid); assert!(new_address.ack_index >= 0); } diff --git a/daemon/identifier/identifier.hpp b/daemon/identifier/identifier.hpp index dd435c3e..a063163e 100644 --- a/daemon/identifier/identifier.hpp +++ b/daemon/identifier/identifier.hpp @@ -30,5 +30,4 @@ struct SyncIdentifier { int index; string kx_public_key; - string friend_request_public_key; }; \ No newline at end of file diff --git a/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql b/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql index 9dd296d0..6601eb20 100644 --- a/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql +++ b/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql @@ -23,47 +23,76 @@ CREATE TABLE registration ( pir_secret_key blob NOT NULL, pir_galois_key blob NOT NULL, authentication_token text NOT NULL, - public_id text NOT NULL -- redundant, but as this is used so prevalent, we'll keep it. + public_id text NOT NULL -- redundant, but as this is used so prevalently, we'll keep it. +); + +-- invitation does not create a friend, because we do not want to pollute friend space with non-friends +CREATE TABLE incoming_invitation ( + public_id text PRIMARY KEY NOT NULL, + message text NOT NULL, + received_at timestamp NOT NULL, ); -- never delete a friend! instead, set `deleted` to true, or else we will lose history! -- (if you actually do delete, you need to also delete from the message tables, or else -- the foreign key constraints will fail) +-- +-- a friend should exist in this table iff it should be possible to send and receive from the friend +-- this is true for all the outgoing invitations and complete friends, but not for incoming invitations. +-- hence, incoming invitations are their own table without a foreign key to friend, whereas all other +-- invitations have a foreign key to friend. CREATE TABLE friend ( uid integer PRIMARY KEY NOT NULL, unique_name text UNIQUE NOT NULL, display_name text NOT NULL, - public_id text NOT NULL, -- redundant, but as this is used so prevalent, we'll keep it. - request_progress integer NOT NULL, -- enum, values defined in db.rs and constants.hpp + invitation_progress integer NOT NULL, -- enum defined in db.rs. outgoingasync, outgoingsync, complete. deleted boolean NOT NULL ); -CREATE TABLE address ( - uid integer PRIMARY KEY NOT NULL, +CREATE TABLE outgoing_sync_invitation ( + friend_uid integer PRIMARY KEY NOT NULL, + story text NOT NULL, + kx_public_key blob NOT NULL, + sent_at timestamp NOT NULL, + FOREIGN KEY(friend_uid) REFERENCES friend(uid) +); + +CREATE TABLE outgoing_async_invitation ( + friend_uid integer PRIMARY KEY NOT NULL, + public_id text NOT NULL, + friend_request_public_key blob NOT NULL, + kx_public_key blob NOT NULL, + message text NOT NULL, + sent_at timestamp NOT NULL, + FOREIGN KEY(friend_uid) REFERENCES friend(uid) +); + +CREATE TABLE complete_friend ( + friend_uid integer PRIMARY KEY NOT NULL, + public_id text NOT NULL, friend_request_public_key blob NOT NULL, - -- this is my message to friend if progress = outgoing, - -- and their message to me if progress = incoming - friend_request_message text NOT NULL, kx_public_key blob NOT NULL, + completed_at timestamp NOT NULL, + FOREIGN KEY(friend_uid) REFERENCES friend(uid) +); + +-- transmission table iff friend is !deleted +CREATE TABLE transmission ( + friend_uid integer PRIMARY KEY NOT NULL, read_index integer NOT NULL, + read_key blob NOT NULL, + write_key blob NOT NULL, -- ack_index is the index into the acking data for this friend -- this NEEDS to be unique for every friend!! -- This needs to be between 0 <= ack_index < MAX_FRIENDS - ack_index integer NOT NULL, - read_key blob NOT NULL, - write_key blob NOT NULL, - FOREIGN KEY(uid) REFERENCES friend(uid) -); - -CREATE TABLE status ( - uid integer PRIMARY KEY NOT NULL, + ack_index integer, -- in the case of an incoming request, this will be NULL -- sent_acked_seqnum is the latest sequence number that was ACKed by the friend -- any message with seqnum > sent_acked_seqnum MUST be retried. sent_acked_seqnum integer NOT NULL, -- received_seqnum is the value that should be ACKed. we guarantee that we -- have received all sequence numbers up to and including this value. received_seqnum integer NOT NULL, - FOREIGN KEY(uid) REFERENCES friend(uid) + FOREIGN KEY(friend_uid) REFERENCES friend(uid) ); -- message includes ALL real messages diff --git a/daemon/rpc/daemon_rpc.cc b/daemon/rpc/daemon_rpc.cc index 8243476d..5e8c1930 100644 --- a/daemon/rpc/daemon_rpc.cc +++ b/daemon/rpc/daemon_rpc.cc @@ -129,8 +129,8 @@ Status DaemonRpc::GetFriendList( new_friend->set_unique_name(std::string(s.unique_name)); new_friend->set_display_name(std::string(s.display_name)); new_friend->set_public_id(std::string(s.public_id)); - new_friend->set_request_progress( - asphrdaemon::FriendRequestProgress::Complete); + new_friend->set_invitation_progress( + asphrdaemon::InvitationProgress::Complete); } } catch (const rust::Error& e) { ASPHR_LOG_ERR("Database failed.", error, e.what(), rpc_call, @@ -181,72 +181,10 @@ Status DaemonRpc::GetMyPublicID( // --------------------------------------- // --------------------------------------- -// || Start: Friend Request Functions || +// || Start: Invitation Functions || // --------------------------------------- // --------------------------------------- -// helper methods to convert -// from RPC structs to DB structs -auto DaemonRpc::convertStructRPCtoDB( - const asphrdaemon::AddAsyncFriendRequest& friend_info, string message, - db::FriendRequestProgress progress, string read_key, string write_key) - -> asphr::StatusOr> { - const int FRIEND_REQUEST_MESSAGE_LENGTH_LIMIT = 500; - // TODO: figure out a reasonable message length limit - if (message.size() > FRIEND_REQUEST_MESSAGE_LENGTH_LIMIT) { - ASPHR_LOG_ERR("Message is too long.", rpc_call, "AddAsyncFriend"); - return absl::InvalidArgumentError("friend request message is too long"); - } - - // We need to decode the friend's id to figure out the public keys of our - // friend - auto friend_public_id_ = crypto::decode_user_id(friend_info.public_id()); - if (!friend_public_id_.ok()) { - ASPHR_LOG_ERR("Failed to decode public ID.", rpc_call, "AddAsyncFriend"); - return absl::InvalidArgumentError("friend requestmessage is too long"); - } - auto [friend_username, friend_allocation, friend_kx_public_key, - friend_request_public_key] = friend_public_id_.value(); - - // construct friend request - return std::pair( - db::FriendFragment{ - .unique_name = friend_info.unique_name(), - .display_name = friend_info.display_name(), - .public_id = friend_info.public_id(), - .request_progress = progress, - .deleted = false, - }, - db::AddAddress{ - .unique_name = friend_info.unique_name(), - .friend_request_public_key = - string_to_rust_u8Vec(friend_request_public_key), - .friend_request_message = message, - .kx_public_key = string_to_rust_u8Vec(friend_kx_public_key), - .read_index = friend_allocation, - .read_key = string_to_rust_u8Vec(read_key), - .write_key = string_to_rust_u8Vec(write_key), - }); -} - -// helper method to convert from DB structs to RPC structs -auto DaemonRpc::convertStructDBtoRPC(const db::Friend& db_friend, - const db::Address& db_address) - -> asphr::StatusOr> { - asphrdaemon::FriendInfo friend_info; - friend_info.set_unique_name(std::string(db_friend.unique_name)); - friend_info.set_display_name(std::string(db_friend.display_name)); - friend_info.set_public_id(std::string(db_friend.public_id)); - - // WARNING: this is SkEtCHy!! - // TODO(sualeh): can we cast this with a proper function on either of the - // structs. - friend_info.set_request_progress( - static_cast( - db_friend.request_progress)); - return std::pair(friend_info, std::string(db_address.friend_request_message)); -} - Status DaemonRpc::AddSyncFriend( grpc::ServerContext* context, const asphrdaemon::AddSyncFriendRequest* addSyncFriendRequest, @@ -268,8 +206,26 @@ Status DaemonRpc::AddSyncFriend( } auto sync_id = sync_id_maybe.value(); - // TODO: not implemented - return Status(grpc::StatusCode::UNIMPLEMENTED, "not implemented"); + auto reg = G.db->get_small_registration(); + auto [read_key, write_key] = crypto::derive_read_write_keys( + rust_u8Vec_to_string(reg.kx_public_key), + rust_u8Vec_to_string(reg.kx_private_key), sync_id.kx_public_key); + + // try to add them to the database + try { + G.db->add_outgoing_sync_invitation( + addSyncFriendRequest->unique_name(), + addSyncFriendRequest->display_name(), addSyncFriendRequest->story(), + string_to_rust_u8Vec(sync_id.kx_public_key), sync_id.index, + string_to_rust_u8Vec(read_key), string_to_rust_u8Vec(write_key), + MAX_FRIENDS); + } catch (const rust::Error& e) { + ASPHR_LOG_ERR("Failed to outgoing sync invitation.", error, e.what(), + rpc_call, "AddSyncFriend"); + return Status(grpc::StatusCode::UNKNOWN, e.what()); + } + + return Status::OK; } grpc::Status DaemonRpc::AddAsyncFriend( @@ -285,40 +241,42 @@ grpc::Status DaemonRpc::AddAsyncFriend( std::string message = addAsyncFriendRequest->message(); - // We need to decode the friend's id to figure out the public keys of our - // friend - auto friend_public_id_ = - crypto::decode_user_id(addAsyncFriendRequest->public_id()); - if (!friend_public_id_.ok()) { - ASPHR_LOG_ERR("Failed to decode public ID.", rpc_call, "AddAsyncFriend"); + if (message.size() > INVITATION_MESSAGE_MAX_PLAINTEXT_SIZE) { + ASPHR_LOG_ERR("Message is too long.", rpc_call, "AddAsyncFriend", + max_length, INVITATION_MESSAGE_MAX_PLAINTEXT_SIZE); return Status(grpc::StatusCode::INVALID_ARGUMENT, - "failed to decode public ID"); + "friend request message is too long"); + } + + auto public_id_maybe = + PublicIdentifier::from_public_id(addAsyncFriendRequest->public_id()); + if (!public_id_maybe.ok()) { + ASPHR_LOG_ERR("Failed to parse public ID.", rpc_call, "AddAsyncFriend", + error_message, public_id_maybe.status().message(), error_code, + public_id_maybe.status().code()); + return Status(grpc::StatusCode::INVALID_ARGUMENT, "invalid public ID"); } - auto [friend_username, friend_allocation, friend_kx_public_key, - friend_request_public_key] = friend_public_id_.value(); + auto public_id = public_id_maybe.value(); + + auto reg = G.db->get_small_registration(); + auto [read_key, write_key] = crypto::derive_read_write_keys( + rust_u8Vec_to_string(reg.kx_public_key), + rust_u8Vec_to_string(reg.kx_private_key), public_id.kx_public_key); // we can now push the request into the database try { - // We also need to do a key exchange to precompute the read/write keys - auto self_kx_public_key = G.db->get_registration().kx_public_key; - auto self_kx_private_key = G.db->get_registration().kx_private_key; - auto key_exchange_ = crypto::derive_read_write_keys( - rust_u8Vec_to_string(self_kx_public_key), - rust_u8Vec_to_string(self_kx_private_key), friend_kx_public_key); - auto [read_key, write_key] = key_exchange_; - auto conversion_result = convertStructRPCtoDB( - *addAsyncFriendRequest, message, - db::FriendRequestProgress::OutgoingAsync, read_key, write_key); - if (!conversion_result.ok()) { - ASPHR_LOG_ERR("Failed to convert RPC to DB.", rpc_call, "AddAsyncFriend"); - return Status(grpc::StatusCode::INVALID_ARGUMENT, - "failed to convert RPC to DB"); - } - auto [db_friend, db_address] = conversion_result.value(); - G.db->add_outgoing_async_friend_requests(db_friend, db_address); + G.db->add_outgoing_async_invitation( + addAsyncFriendRequest->unique_name(), + addAsyncFriendRequest->display_name(), + addAsyncFriendRequest->public_id(), + string_to_rust_u8Vec(public_id.friend_request_public_key), + string_to_rust_u8Vec(public_id.kx_public_key), + addAsyncFriendRequest->message(), public_id.index, + string_to_rust_u8Vec(read_key), string_to_rust_u8Vec(write_key), + MAX_FRIENDS); } catch (const rust::Error& e) { - ASPHR_LOG_ERR("Failed to add friend.", error, e.what(), rpc_call, - "AddAsyncFriend"); + ASPHR_LOG_ERR("Failed to add outgoing async invitation.", error, e.what(), + rpc_call, "AddAsyncFriend"); return Status(grpc::StatusCode::UNKNOWN, e.what()); } ASPHR_LOG_INFO("AddAsyncFriend() done.", rpc_call, "AddAsyncFriend"); @@ -334,8 +292,38 @@ grpc::Status DaemonRpc::GetOutgoingSyncInvitations( ASPHR_LOG_INFO("GetOutgoingSyncInvitations() called.", rpc_call, "GetOutgoingSyncInvitations"); - // unimplemented - return Status(grpc::StatusCode::UNIMPLEMENTED, "not implemented"); + if (!G.db->has_registered()) { + ASPHR_LOG_INFO("Need to register first.", rpc_call, + "GetOutgoingSyncInvitations"); + return Status(grpc::StatusCode::UNAUTHENTICATED, "not registered"); + } + + try { + auto outgoing_sync_invitations = G.db->get_outgoing_sync_invitations(); + for (auto outgoing_sync_invitation : outgoing_sync_invitations) { + auto invitation = getOutgoingSyncInvitationsResponse->add_invitations(); + invitation->set_unique_name(string(outgoing_sync_invitation.unique_name)); + invitation->set_display_name( + string(outgoing_sync_invitation.display_name)); + invitation->set_story(string(outgoing_sync_invitation.story)); + // convert outgoing_sync_invitation.sent_at + auto sent_at = absl::FromUnixMicros(outgoing_sync_invitation.sent_at); + auto timestamp_str = absl::FormatTime(sent_at); + auto timestamp = invitation->mutable_sent_at(); + using TimeUtil = google::protobuf::util::TimeUtil; + auto success = TimeUtil::FromString(timestamp_str, timestamp); + if (!success) { + ASPHR_LOG_ERR("Failed to parse timestamp.", error, timestamp_str, + rpc_call, "GetOutgoingSyncInvitations"); + return Status(grpc::StatusCode::UNKNOWN, "invalid timestamp"); + } + } + } catch (const rust::Error& e) { + ASPHR_LOG_ERR("Failed to get outgoing friend requests.", error, e.what(), + rpc_call, "GetOutgoingSyncInvitations"); + return Status(grpc::StatusCode::UNKNOWN, e.what()); + } + return Status::OK; } Status DaemonRpc::GetOutgoingAsyncInvitations( @@ -354,46 +342,32 @@ Status DaemonRpc::GetOutgoingAsyncInvitations( } try { - // call rust db to get all outgoing friend requests - auto outgoing_requests = G.db->get_outgoing_async_friend_requests(); - for (auto db_friend : outgoing_requests) { - // get the corresponding address from the db - auto address = G.db->get_friend_address(db_friend.uid); - // convert to RPC structs - auto conversion_result = convertStructDBtoRPC(db_friend, address); - if (!conversion_result.ok()) { - ASPHR_LOG_ERR("Failed to convert DB to RPC.", rpc_call, - "GetOutgoingAsyncInvitations"); - return Status(grpc::StatusCode::UNKNOWN, "failed to convert DB to RPC"); - } - auto [friend_info, message] = conversion_result.value(); - // add to response + auto outgoing_async_invitations = G.db->get_outgoing_async_invitations(); + for (auto outgoing_async_invitation : outgoing_async_invitations) { auto invitation = getOutgoingAsyncInvitationsResponse->add_invitations(); - - /** - * message OutgoingAsyncInvitationInfo { - string unique_name = 1; - string display_name = 2; - string public_id = 3; - string message = 4; - google.protobuf.Timestamp sent_at = 5; - } - */ - invitation->set_unique_name(friend_info.unique_name()); - invitation->set_display_name(friend_info.display_name()); - invitation->set_public_id(friend_info.public_id()); - invitation->set_message(message); - // set now - // invitation->set_sent_at( - // google::protobuf::util::TimeUtil::SecondsToTimestamp( - // std::time(nullptr))); + invitation->set_unique_name( + string(outgoing_async_invitation.unique_name)); + invitation->set_display_name( + string(outgoing_async_invitation.display_name)); + invitation->set_public_id(string(outgoing_async_invitation.public_id)); + invitation->set_message(string(outgoing_async_invitation.message)); + // convert outgoing_async_invitation.sent_at + auto sent_at = absl::FromUnixMicros(outgoing_async_invitation.sent_at); + auto timestamp_str = absl::FormatTime(sent_at); + auto timestamp = invitation->mutable_sent_at(); + using TimeUtil = google::protobuf::util::TimeUtil; + auto success = TimeUtil::FromString(timestamp_str, timestamp); + if (!success) { + ASPHR_LOG_ERR("Failed to parse timestamp.", error, timestamp_str, + rpc_call, "GetOutgoingAsyncInvitations"); + return Status(grpc::StatusCode::UNKNOWN, "invalid timestamp"); + } } } catch (const rust::Error& e) { ASPHR_LOG_ERR("Failed to get outgoing friend requests.", error, e.what(), rpc_call, "GetOutgoingAsyncFriendRequests"); return Status(grpc::StatusCode::UNKNOWN, e.what()); } - // TODO: add sync friend requests return Status::OK; } @@ -403,42 +377,36 @@ Status DaemonRpc::GetIncomingAsyncInvitations( getIncomingAsyncInvitationsRequest, asphrdaemon::GetIncomingAsyncInvitationsResponse* getIncomingAsyncInvitationsResponse) { - // clone of the above + // essentially a clone of the getoutgoingfriendrequests rpc + ASPHR_LOG_INFO("GetIncomingAsyncInvitations() called.", rpc_call, + "GetIncomingAsyncInvitations"); + + if (!G.db->has_registered()) { + ASPHR_LOG_INFO("Need to register first.", rpc_call, + "GetIncomingAsyncInvitations"); + return Status(grpc::StatusCode::UNAUTHENTICATED, "not registered"); + } + try { - // call rust db to get all incoming friend requests - auto incoming_requests = G.db->get_incoming_async_friend_requests(); - for (auto db_friend : incoming_requests) { - // get the corresponding address from the db - auto address = G.db->get_friend_address(db_friend.uid); - // convert to RPC structs - auto conversion_result = convertStructDBtoRPC(db_friend, address); - if (!conversion_result.ok()) { - ASPHR_LOG_ERR("Failed to convert DB to RPC.", rpc_call, - "GetIncomingAsyncFriendRequests"); - return Status(grpc::StatusCode::UNKNOWN, "failed to convert DB to RPC"); - } - auto [friend_info, message] = conversion_result.value(); - // add to response + auto incoming_invitations = G.db->get_incoming_invitations(); + for (auto incoming_invitation : incoming_invitations) { auto invitation = getIncomingAsyncInvitationsResponse->add_invitations(); - - /** - * message IncomingAsyncInvitationInfo { - string public_id = 1; - string message = 2; - google.protobuf.Timestamp received_at = 3; - } - */ - - invitation->set_public_id("friend_info.public_id"); - invitation->set_message(message); - // set now - // invitation->set_received_at( - // google::protobuf::util::TimeUtil::SecondsToTimestamp( - // std::time(nullptr))); + invitation->set_public_id(string(incoming_invitation.public_id)); + invitation->set_message(string(incoming_invitation.message)); + auto received_at = absl::FromUnixMicros(incoming_invitation.received_at); + auto timestamp_str = absl::FormatTime(received_at); + auto timestamp = invitation->mutable_received_at(); + using TimeUtil = google::protobuf::util::TimeUtil; + auto success = TimeUtil::FromString(timestamp_str, timestamp); + if (!success) { + ASPHR_LOG_ERR("Failed to parse timestamp.", error, timestamp_str, + rpc_call, "GetIncomingAsyncInvitations"); + return Status(grpc::StatusCode::UNKNOWN, "invalid timestamp"); + } } } catch (const rust::Error& e) { - ASPHR_LOG_ERR("Failed to get incoming friend requests.", error, e.what(), - rpc_call, "GetIncomingAsyncFriendRequests"); + ASPHR_LOG_ERR("Failed to get incoming invitations.", error, e.what(), + rpc_call, "GetIncomingAsyncInvitations"); return Status(grpc::StatusCode::UNKNOWN, e.what()); } return Status::OK; @@ -449,8 +417,46 @@ Status DaemonRpc::AcceptAsyncInvitation( const asphrdaemon::AcceptAsyncInvitationRequest* acceptAsyncInvitationRequest, asphrdaemon::AcceptAsyncInvitationResponse* acceptAsyncInvitationResponse) { - // unimplemented - return Status(grpc::StatusCode::UNIMPLEMENTED, "not implemented"); + ASPHR_LOG_INFO("AcceptAsyncInvitation() called.", rpc_call, + "AcceptAsyncInvitation"); + + if (!G.db->has_registered()) { + ASPHR_LOG_INFO("Need to register first.", rpc_call, + "AcceptAsyncInvitation"); + return Status(grpc::StatusCode::UNAUTHENTICATED, "not registered"); + } + try { + auto public_id_maybe = PublicIdentifier::from_public_id( + acceptAsyncInvitationRequest->public_id()); + if (!public_id_maybe.ok()) { + ASPHR_LOG_ERR("Failed to parse public ID.", rpc_call, + "AcceptAsyncInvitation", error_message, + public_id_maybe.status().message(), error_code, + public_id_maybe.status().code()); + return Status(grpc::StatusCode::INVALID_ARGUMENT, "invalid public ID"); + } + auto public_id = public_id_maybe.value(); + + auto reg = G.db->get_small_registration(); + auto [read_key, write_key] = crypto::derive_read_write_keys( + rust_u8Vec_to_string(reg.kx_public_key), + rust_u8Vec_to_string(reg.kx_private_key), public_id.kx_public_key); + + G.db->accept_incoming_invitation( + acceptAsyncInvitationRequest->public_id(), + acceptAsyncInvitationRequest->unique_name(), + acceptAsyncInvitationRequest->display_name(), + string_to_rust_u8Vec(public_id.friend_request_public_key), + string_to_rust_u8Vec(public_id.kx_public_key), public_id.index, + string_to_rust_u8Vec(read_key), string_to_rust_u8Vec(write_key), + MAX_FRIENDS); + } catch (const rust::Error& e) { + ASPHR_LOG_ERR("Failed to accept async friend request.", error, e.what(), + rpc_call, "AcceptAsyncInvitation"); + return Status(grpc::StatusCode::UNKNOWN, e.what()); + } + + return Status::OK; } Status DaemonRpc::RejectAsyncInvitation( @@ -458,8 +464,25 @@ Status DaemonRpc::RejectAsyncInvitation( const asphrdaemon::RejectAsyncInvitationRequest* rejectAsyncInvitationRequest, asphrdaemon::RejectAsyncInvitationResponse* rejectAsyncInvitationResponse) { - // unimplemented - return Status(grpc::StatusCode::UNIMPLEMENTED, "not implemented"); + ASPHR_LOG_INFO("RejectAsyncInvitation() called.", rpc_call, + "RejectAsyncInvitation"); + + if (!G.db->has_registered()) { + ASPHR_LOG_INFO("Need to register first.", rpc_call, + "RejectAsyncInvitation"); + return Status(grpc::StatusCode::UNAUTHENTICATED, "not registered"); + } + + try { + // call rust db to reject the friend request + G.db->deny_incoming_invitation(rejectAsyncInvitationRequest->public_id()); + } catch (const rust::Error& e) { + ASPHR_LOG_ERR("Failed to reject async friend request.", error, e.what(), + rpc_call, "RejectAsyncInvitation"); + return Status(grpc::StatusCode::UNKNOWN, e.what()); + } + + return Status::OK; } Status DaemonRpc::RemoveFriend( diff --git a/daemon/rpc/daemon_rpc.hpp b/daemon/rpc/daemon_rpc.hpp index 9d58c7f7..588ce3c7 100644 --- a/daemon/rpc/daemon_rpc.hpp +++ b/daemon/rpc/daemon_rpc.hpp @@ -138,16 +138,4 @@ class DaemonRpc final : public asphrdaemon::Daemon::Service { private: Global& G; shared_ptr stub; - - // The RPC speaks in the FriendInfo Struct - // The DB speaks in the Friend and Address Structs - // we provide a way to translate between them - auto convertStructRPCtoDB( - const asphrdaemon::AddAsyncFriendRequest& friend_info, string message, - db::FriendRequestProgress progress, string read_key, string write_key) - -> asphr::StatusOr>; - - auto convertStructDBtoRPC(const db::Friend& db_friend, - const db::Address& db_address) - -> asphr::StatusOr>; }; \ No newline at end of file diff --git a/daemon/transmitter/transmitter.cc b/daemon/transmitter/transmitter.cc index bc03ff9a..3b23d8ef 100644 --- a/daemon/transmitter/transmitter.cc +++ b/daemon/transmitter/transmitter.cc @@ -502,7 +502,7 @@ auto Transmitter::batch_retrieve_pir(FastPIRClient& client, //---------------------------------------------------------------- //---------------------------------------------------------------- -auto Transmitter::transmit_async_friend_request() -> void { +auto Transmitter::transmit_async_invitation() -> void { // retrieve the friend request from DB std::vector async_friend_requests = {}; std::vector async_friend_requests_address = {}; @@ -584,7 +584,7 @@ auto Transmitter::transmit_async_friend_request() -> void { // 3. If the friend is marked as incoming, then we ignore the request. // 4. If the friend is marked as outgoing, then we approve this request // immediately. -auto Transmitter::retrieve_async_friend_request(int start_index, int end_index) +auto Transmitter::retrieve_async_invitations(int start_index, int end_index) -> void { // check input if (start_index < 0 || end_index < 0 || start_index > end_index || diff --git a/daemon/transmitter/transmitter.hpp b/daemon/transmitter/transmitter.hpp index 6bebc4da..f0de1c38 100644 --- a/daemon/transmitter/transmitter.hpp +++ b/daemon/transmitter/transmitter.hpp @@ -29,8 +29,10 @@ class Transmitter { auto send() -> void; // method for testing - // because during microtests, we do not want to full db scan - auto reset_async_scanner(int index) { next_async_friend_request_retrieve_index = index; } + // because during small tests, we generally do not want to full db scan + auto reset_async_scanner(int index) { + next_async_friend_request_retrieve_index = index; + } private: Global& G; @@ -56,7 +58,7 @@ class Transmitter { optional previous_success_receive_friend; optional just_acked_friend; - // used to crawl the db + // used to crawl the db for the async invitations int next_async_friend_request_retrieve_index; // for each index, get the PIR response for that index @@ -69,7 +71,7 @@ class Transmitter { // transmit async friend request to the server // we must reencrypt each round, to avoid // replaying the same message to the server - auto transmit_async_friend_request() -> void; + auto transmit_async_invitation() -> void; // retrieve and process async friend request from the server // and push them to the database @@ -81,7 +83,7 @@ class Transmitter { // 3. If the friend is marked as incoming, then we ignore the request. // 4. If the friend is marked as outgoing, then we approve this request // immediately. - auto retrieve_async_friend_request(int start_index, int end_index) -> void; + auto retrieve_async_invitations(int start_index, int end_index) -> void; auto check_rep() const noexcept -> void; }; \ No newline at end of file From 6a8ac2e21cf2f81c0c612b9240b97ad39e154e79 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 00:18:04 +0000 Subject: [PATCH 02/22] =?UTF-8?q?=F0=9F=94=84=20"change=20to=20system=20me?= =?UTF-8?q?ssage=20"=20Update=20anysphere/asphr=20commit=20SHA=20?= =?UTF-8?q?=F0=9F=94=97=20https://github.com/anysphere/asphr/commit/4415fe?= =?UTF-8?q?b0985123d7031924f7298f9b78b3db058f?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WORKSPACE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKSPACE b/WORKSPACE index 9b8a781d..de33ca76 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -7,7 +7,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( name = "asphr", - commit = "32a45d0dd104c1115efdbdc98d0e44ebfd7f3205", # autoupdate anysphere/asphr + commit = "4415feb0985123d7031924f7298f9b78b3db058f", # autoupdate anysphere/asphr init_submodules = True, remote = "https://github.com/anysphere/asphr.git", ) From 163b7725bad7847cea07ead3709df149e065acd0 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 18:05:17 -0700 Subject: [PATCH 03/22] make transmitter use the new db types --- daemon/db/db.rs | 33 +++++++---- daemon/transmitter/transmitter.cc | 90 ++++++++++++++++++------------ daemon/transmitter/transmitter.hpp | 4 +- 3 files changed, 80 insertions(+), 47 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index da6fd7e5..5991b6c9 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -236,6 +236,8 @@ pub mod ffi { pub display_name: String, pub invitation_progress: InvitationProgress, pub public_id: String, + pub friend_request_public_key: Vec, + pub kx_public_key: Vec, pub message: String, pub sent_at: i64, // unix micros } @@ -280,10 +282,13 @@ pub mod ffi { #[derive(Queryable)] struct SmallRegistrationFragment { pub uid: i32, + pub friend_request_public_key: Vec, + pub friend_request_private_key: Vec, pub kx_public_key: Vec, pub kx_private_key: Vec, pub allocation: i32, pub authentication_token: String, + pub public_id: String, } #[derive(Queryable, Insertable)] @@ -474,9 +479,14 @@ pub mod ffi { write_key: Vec, max_friends: i32, // this is the constant defined in constants.hpp. TODO(sualeh): move it to the ffi. ) -> Result; - // if an invitation with the same public_id already exists, we replace the message - // if an outgoing invitation with the same public_id already exists, we automatically make them become friends - // if a friend with the same public_id already exists or is deleted, we ignore + // It is important to define the behavior of this function in the case of + // duplicate requests. i.e. when a friend (request) with the same public key + // is already in the database. Here's the definition for now. + // 1. If the friend is marked as deleted, then we ignore the request. + // 2. If the friend is marked as accepted, then we ignore the request. + // 3. If the friend is marked as incoming, then we update the message. + // 4. If the friend is marked as outgoing, then we approve this request + // immediately. fn add_incoming_async_invitation(&self, public_id: &str, message: &str) -> Result<()>; // get invitations fn get_outgoing_sync_invitations(&self) -> Result>; @@ -732,10 +742,13 @@ impl DB { let q = registration::table.select(( registration::uid, + registration::friend_request_public_key, + registration::friend_request_private_key, registration::kx_public_key, registration::kx_private_key, registration::allocation, registration::authentication_token, + registration::public_id, )); let registration = q .first::(&mut conn) @@ -1195,16 +1208,14 @@ impl DB { let mut conn = self.connect()?; use crate::schema::address; use crate::schema::friend; - use crate::schema::status; - let wide_friends = friend::table - .filter(friend::deleted.eq(false)) - .inner_join(status::table) - .inner_join(address::table); + use crate::schema::transmission; + let wide_friends = + friend::table.filter(friend::deleted.eq(false)).inner_join(transmission::table); let q = wide_friends.select(( friend::uid, - status::received_seqnum, - address::write_key, - address::ack_index, + transmission::received_seqnum, + transmission::write_key, + transmission::ack_index, )); let r = q.load::(&mut conn); match r { diff --git a/daemon/transmitter/transmitter.cc b/daemon/transmitter/transmitter.cc index 3b23d8ef..9c60f951 100644 --- a/daemon/transmitter/transmitter.cc +++ b/daemon/transmitter/transmitter.cc @@ -9,8 +9,7 @@ #include "schema/server.grpc.pb.h" -auto generate_dummy_address(const Global& G, const db::Registration& reg) - -> db::Address { +auto generate_dummy_address(const db::Registration& reg) -> db::Address { auto dummy_friend_keypair = crypto::generate_kx_keypair(); // convert reg.kx_public_key, kx_private_key to a string @@ -25,14 +24,28 @@ auto generate_dummy_address(const Global& G, const db::Registration& reg) auto read_key_vec_rust = string_to_rust_u8Vec(dummy_read_write_keys.first); auto write_key_vec_rust = string_to_rust_u8Vec(dummy_read_write_keys.second); - return (db::Address){-1, - reg.friend_request_public_key, - "this is a dummy", - reg.kx_public_key, - 0, - 0, - read_key_vec_rust, - write_key_vec_rust}; + return (db::Address){-1, 0, read_key_vec_rust, write_key_vec_rust, 0}; +} + +auto generate_dummy_async_invitation() -> db::OutgoingAsyncInvitation { + auto dummy_kx_keypair = crypto::generate_kx_keypair(); + auto kx_public_key = rust_u8Vec_to_string(dummy_kx_keypair.first); + + auto dummy_friend_request_keypair = crypto::generate_friend_request_keypair(); + auto friend_request_public_key = + rust_u8Vec_to_string(dummy_friend_request_keypair.first); + + auto public_id = + PublicIdentifier(0, kx_public_key, friend_request_public_key); + auto public_id_str = public_id.to_public_id(); + + return db::OutgoingAsyncInvitation { + .friend_uid = -1, .unique_name = "dummy", .display_name = "Dummy", + .invitation_progress = db::InvitationProgress::OutgoingAsync, + .public_id = public_id_str, + .friend_request_public_key = friend_request_public_key, + .kx_public_key = kx_public_key, .message = "Hello dummy", .sent_at = 0, + } } Transmitter::Transmitter(Global& G, shared_ptr stub) @@ -55,6 +68,7 @@ auto Transmitter::setup_registration_caching() -> void { cached_pir_client_secret_key = rust_u8Vec_to_string(reg.pir_secret_key); dummy_address = generate_dummy_address(G, reg); + dummy_outgoing_invitation = generate_dummy_async_invitation(G, reg); } check_rep(); @@ -503,31 +517,31 @@ auto Transmitter::batch_retrieve_pir(FastPIRClient& client, //---------------------------------------------------------------- auto Transmitter::transmit_async_invitation() -> void { + ASPHR_ASSERT(dummy_outgoing_invitation.has_value()); + // retrieve the friend request from DB - std::vector async_friend_requests = {}; - std::vector async_friend_requests_address = {}; + std::vector invitations = {}; try { - auto async_friend_requests_rs = G.db->get_outgoing_async_friend_requests(); - for (auto db_friend : async_friend_requests_rs) { - async_friend_requests.push_back(db_friend); - async_friend_requests_address.push_back( - G.db->get_friend_address(db_friend.uid)); - } + invitations = G.db->get_outgoing_async_invitations(); } catch (const rust::Error& e) { - ASPHR_LOG_INFO("No async friend requests to send (probably).", error_msg, - e.what()); - return; + ASPHR_LOG_ERR("Failed to get outgoing async invitations.", error_msg, + e.what()); + // TODO: alert the user saying that their invitation may be delayed } // TODO: allow us to send multiple friend requests at once // Right now, we only send one friend request at once // We can change this later if we want to. - // TODO: send a dummy friend request if we have no friend requests to send - if (async_friend_requests.size() == 0) { - return; + + if (invitations.size() == 0) { + invitations.push_back(dummy_outgoing_invitation.value()); } + + ASPHR_ASSERT_EQ_MSG( + invitations.size(), 1, + "We only support one outgoing invitation at once at the moment."); + // send the friend request - db::Friend async_friend = async_friend_requests[0]; - db::Address async_friend_address = async_friend_requests_address[0]; + db::OutgoingAsyncInvitation invitation = invitations.at(0); // we need to compute the id for both parties // We declare these outside cause I don't want to everything wrapped in @@ -535,27 +549,31 @@ auto Transmitter::transmit_async_invitation() -> void { string my_id; // we could probably cache this in DB, but we don't need to string my_friend_request_private_key; string friend_id; - db::Registration reg_info; + db::SmallRegistrationFragment reg_info; try { - reg_info = G.db->get_registration(); + reg_info = G.db->get_small_registration(); my_id = std::string(reg_info.public_id); my_friend_request_private_key = rust_u8Vec_to_string(reg_info.friend_request_private_key); - friend_id = std::string(async_friend.public_id); + friend_id = std::string(invitation.public_id); } catch (const rust::Error& e) { - // ASPHR_LOG_ERR("Could not generate user ID.", error_msg, e.what()); + ASPHR_LOG_ERR("Could not get registration.", error_msg, e.what()); return; } + // encrypt the friend request auto encrypted_friend_request_status_ = crypto::encrypt_async_friend_request( my_id, my_friend_request_private_key, friend_id, - std::string(async_friend_address.friend_request_message)); + std::string(invitation.friend_request_message)); + if (!encrypted_friend_request_status_.ok()) { - ASPHR_LOG_ERR("Error encrypting async friend request: ", "Error Message", + ASPHR_LOG_ERR("Error encrypting async friend request: ", error_msg, encrypted_friend_request_status_.status().ToString()); return; } + auto encrypted_friend_request = encrypted_friend_request_status_.value(); + // Send to server asphrserver::AddFriendAsyncInfo request; request.set_index(reg_info.allocation); @@ -565,13 +583,12 @@ auto Transmitter::transmit_async_invitation() -> void { grpc::ClientContext context; grpc::Status status = stub->AddFriendAsync(&context, request, &reply); if (status.ok()) { - ASPHR_LOG_INFO("Async Friend Request sent to server!"); + ASPHR_LOG_INFO("Async invitation sent to server."); } else { ASPHR_LOG_ERR( "Communication with server failed when sending friend request", - "error_code", status.error_code(), "details", status.error_details()); + error_code, status.error_code(), details, status.error_details()); } - return; } // retrieve and process async friend request from the server @@ -765,4 +782,7 @@ auto Transmitter::check_rep() const noexcept -> void { "cached_pir_client and cached_pir_client_status are not in sync"); ASPHR_ASSERT_EQ_MSG(cached_pir_client.has_value(), dummy_address.has_value(), "cached_pir_client and dummy_address are not in sync"); + ASPHR_ASSERT_EQ_MSG( + cached_pir_client.has_value(), dummy_outgoing_invitation.has_value(), + "cached_pir_client and dummy_outgoing_invitation are not in sync"); } \ No newline at end of file diff --git a/daemon/transmitter/transmitter.hpp b/daemon/transmitter/transmitter.hpp index f0de1c38..668a269f 100644 --- a/daemon/transmitter/transmitter.hpp +++ b/daemon/transmitter/transmitter.hpp @@ -46,6 +46,7 @@ class Transmitter { // actual friend to send to. This is critically important in order to // not leak metadata!! optional dummy_address; + optional dummy_outgoing_invitation; // some caching work the first time we set up, setting up the things above auto setup_registration_caching() -> void; @@ -80,7 +81,8 @@ class Transmitter { // is already in the database. Here's the definition for now. // 1. If the friend is marked as deleted, then we ignore the request. // 2. If the friend is marked as accepted, then we ignore the request. - // 3. If the friend is marked as incoming, then we ignore the request. + // 3. If the friend is marked as incoming, then we update the invitation with + // the new message. // 4. If the friend is marked as outgoing, then we approve this request // immediately. auto retrieve_async_invitations(int start_index, int end_index) -> void; From 4f75d8ba5121c75374618996d8a8d5d6fe8ec09a Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 18:23:56 -0700 Subject: [PATCH 04/22] finish transmitter changes --- daemon/crypto/crypto.cc | 3 + daemon/db/db.rs | 2 +- daemon/transmitter/transmitter.cc | 146 +++++++----------------------- 3 files changed, 36 insertions(+), 115 deletions(-) diff --git a/daemon/crypto/crypto.cc b/daemon/crypto/crypto.cc index 1ba61015..7c205762 100644 --- a/daemon/crypto/crypto.cc +++ b/daemon/crypto/crypto.cc @@ -548,6 +548,9 @@ auto decrypt_async_friend_request_public_key_only( // TODO: insert additional checks here // read the allocation // create the friend + // TODO: specifically, we need to verify that the public_id in the body + // corresponds to the public_id that the message was authenticated with + // otherwise, someone might impersonate the real receiver return std::make_pair(split_plaintext[0], split_plaintext[1]); } } // namespace crypto \ No newline at end of file diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 5991b6c9..a9b8770b 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -485,7 +485,7 @@ pub mod ffi { // 1. If the friend is marked as deleted, then we ignore the request. // 2. If the friend is marked as accepted, then we ignore the request. // 3. If the friend is marked as incoming, then we update the message. - // 4. If the friend is marked as outgoing, then we approve this request + // 4. If the friend is marked as outgoing, then we approve this request. // immediately. fn add_incoming_async_invitation(&self, public_id: &str, message: &str) -> Result<()>; // get invitations diff --git a/daemon/transmitter/transmitter.cc b/daemon/transmitter/transmitter.cc index 9c60f951..79a9b0d1 100644 --- a/daemon/transmitter/transmitter.cc +++ b/daemon/transmitter/transmitter.cc @@ -362,7 +362,7 @@ auto Transmitter::retrieve() -> void { ASYNC_FRIEND_REQUEST_BATCH_SIZE, CLIENT_DB_ROWS); // call the server to retrieve the async friend requests - retrieve_async_friend_request(start_index, end_index); + retrieve_async_invitations(start_index, end_index); if (end_index == CLIENT_DB_ROWS) { next_async_friend_request_retrieve_index = 0; } else { @@ -472,7 +472,7 @@ auto Transmitter::send() -> void { server_status_code, status.error_code(), server_status_message, status.error_message()); } - transmit_async_friend_request(); + transmit_async_invitation(); check_rep(); } @@ -604,13 +604,15 @@ auto Transmitter::transmit_async_invitation() -> void { auto Transmitter::retrieve_async_invitations(int start_index, int end_index) -> void { // check input - if (start_index < 0 || end_index < 0 || start_index > end_index || - end_index - start_index > ASYNC_FRIEND_REQUEST_BATCH_SIZE) { - // TODO: I should ask arvid how to use ASPHR_LOG - // ASPHR_LOG_ERR("Invalid input for retrieve_async_friend_request.", - // start_index, end_index); - return; - } + ASPHR_LOG_INFO("Retrieving async invitations.", start_index, start_index, + end_index, end_index); + ASPHR_ASSERT_MSG(start_index >= 0, "start_index must be >= 0"); + ASPHR_ASSERT_MSG(end_index >= 0, "end_index must be >= 0"); + ASPHR_ASSERT_MSG(start_index <= end_index, + "start_index must be <= end_index"); + ASPHR_ASSERT_MSG( + end_index - start_index <= ASYNC_FRIEND_REQUEST_BATCH_SIZE, + "end_index - start_index must be <= ASYNC_FRIEND_REQUEST_BATCH_SIZE"); // STEP 1: ask the server for all the friend database entries asphrserver::GetAsyncFriendRequestsInfo request; @@ -621,26 +623,26 @@ auto Transmitter::retrieve_async_invitations(int start_index, int end_index) grpc::ClientContext context; grpc::Status status = stub->GetAsyncFriendRequests(&context, request, &reply); if (!status.ok()) { - std::stringstream ss; - ss << status.error_code() << ": " << status.error_message() - << " details:" << status.error_details() << std::endl; - - // ASPHR_LOG_ERR("Could not retrieve async friend requests.", error_msg, - // ss.str()); + ASPHR_LOG_ERR("Could not retrieve async friend requests.", error_code, + status.error_code(), error_message, status.error_message(), + error_details, status.error_details()); + return; } if (reply.friend_request_public_key_size() != reply.requests_size()) { ASPHR_LOG_ERR("Response is malformed!", "size1:", reply.friend_request_public_key_size(), "size2:", reply.requests_size()); + return; } + // Step 2: iterate over the returned friend requests // we need to obtain our own id here string my_id; string my_friend_request_private_key; - db::Registration reg_info; + db::SmallRegistrationFragment reg_info; try { - reg_info = G.db->get_registration(); + reg_info = G.db->get_small_registration(); my_id = std::string(reg_info.public_id); my_friend_request_private_key = rust_u8Vec_to_string(reg_info.friend_request_private_key); @@ -649,6 +651,7 @@ auto Transmitter::retrieve_async_invitations(int start_index, int end_index) e.what()); return; } + std::map friends = {}; for (int i = 0; i < reply.requests_size(); i++) { // Step 2.1: test if the friend request is meant for us @@ -661,114 +664,29 @@ auto Transmitter::retrieve_async_invitations(int start_index, int end_index) my_id, my_friend_request_private_key, friend_public_key, friend_request); if (!decrypted_friend_request_status.ok()) { - // not meant for us! + // not meant for us! this is ok. the expected outcome. continue; } // the friend request is meant for us // Step 2.2: unpack the friend request - auto [friend_id, friend_message] = decrypted_friend_request_status.value(); + auto [friend_public_id_str, friend_message] = + decrypted_friend_request_status.value(); ASPHR_LOG_INFO("Found async friend request!", public_id, friend_id, message, friend_message); // unpack the friend id - auto friend_id_unpacked_ = crypto::decode_user_id(friend_id); - if (!friend_id_unpacked_.ok()) { - // ASPHR_LOG_ERR("Read Malformed friend ID.", "Err", - // friend_id_unpacked_.status()); - continue; - } - auto [friend_name, friend_allocation, friend_kx_public_key, - friend_request_public_key] = friend_id_unpacked_.value(); - // currently the name is not transmitted. Check that - if (friend_name != "") { - // ASPHR_LOG_ERR("Friend name is not empty.", "Err", friend_name); - continue; - } - // do the key exchange here. - auto my_kx_public_key = rust_u8Vec_to_string(reg_info.kx_public_key); - auto my_kx_private_key = rust_u8Vec_to_string(reg_info.kx_private_key); - // derive read and write keys. - auto [read_key, write_key] = crypto::derive_read_write_keys( - my_kx_public_key, my_kx_private_key, friend_kx_public_key); - - // Step 2.3: check for duplicate requests - // 1. If the friend is marked as deleted, then we ignore the request. - // 2. If the friend is marked as accepted, then we ignore the request. - // 3. If the friend is marked as incoming, then we ignore the request. - // 4. If the friend is marked as outgoing, then we approve this request - - // I'll just scan over the entire friend db - // This will be changed later - - try { - auto all_friends = G.db->get_friends_all_status(); - for (auto db_friend : all_friends) { - // check the id - if (db_friend.public_id == friend_id) { - // check the status - if (db_friend.deleted) { - // ignore this request - ASPHR_LOG_INFO("Ignoring friend request because friend is deleted.", - "Friend ID", friend_id); - continue; - } else { - switch (db_friend.request_progress) { - case db::FriendRequestProgress::Complete: - // ignore this request - ASPHR_LOG_INFO( - "Ignoring friend request because friend is accepted.", - "Friend ID", friend_id); - break; - case db::FriendRequestProgress::Incoming: - // ignore this request - ASPHR_LOG_INFO( - "Ignoring friend request because friend is incoming.", - "Friend ID", friend_id); - break; - case db::FriendRequestProgress::OutgoingAsync: - case db::FriendRequestProgress::OutgoingSync: - // approve this request - ASPHR_LOG_INFO( - "Approving friend request because friend is outgoing.", - "Friend ID", friend_id); - // update the friend status - G.db->approve_async_friend_request(db_friend.unique_name, - MAX_FRIENDS); - break; - } - continue; - } - } - } - } catch (const rust::Error& e) { - // ASPHR_LOG_ERR("Could not find friend ID.", "Error", e.what()); + auto friend_public_id_status = + PublicIdentifier::from_public_id(friend_public_id_str); + if (!friend_public_id_status.ok()) { + ASPHR_LOG_ERR("Read malformed friend public ID.", error_message, + friend_public_id_status.status().message()); continue; } - // Step 2.4: if we get here, then we have a new friend request - // we need to create a new friend entry in the database - // Note: the current design decision is to not transmit the actual name of - // the friend - // so we just set both unique name and display name to public id - db::FriendFragment new_friend = { - .unique_name = friend_id, - .display_name = friend_id, - .public_id = friend_id, - .request_progress = db::FriendRequestProgress::Incoming, - .deleted = false}; - - db::AddAddress new_address = { - .unique_name = friend_id, - .friend_request_public_key = - string_to_rust_u8Vec(friend_request_public_key), - .friend_request_message = friend_message, - .kx_public_key = string_to_rust_u8Vec(friend_kx_public_key), - .read_index = friend_allocation, - .read_key = string_to_rust_u8Vec(read_key), - .write_key = string_to_rust_u8Vec(write_key), - }; + // now we can insert this invitation in the DB!!! very very exciting :) try { - G.db->add_incoming_async_friend_requests(new_friend, new_address); + G.db->add_incoming_async_invitation(friend_public_id_str, friend_message); } catch (const rust::Error& e) { - ASPHR_LOG_ERR("Could not add friend.", Error, e.what()); + ASPHR_LOG_ERR("Error when adding async invitation.", error_message, + e.what()); continue; } } From 23f63a43fad3d33a804450e199a6e7accf1194ce Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 19:47:39 -0700 Subject: [PATCH 05/22] it builds --- daemon/crypto/constants.hpp | 19 ++- daemon/db/db.rs | 136 ++++++++++++------ daemon/db/schema.rs | 10 +- daemon/db/tests.rs | 85 +++++------ .../down.sql | 7 +- .../up.sql | 8 +- daemon/transmitter/transmitter.cc | 94 ++++++++---- 7 files changed, 212 insertions(+), 147 deletions(-) diff --git a/daemon/crypto/constants.hpp b/daemon/crypto/constants.hpp index 1348ee36..602496fa 100644 --- a/daemon/crypto/constants.hpp +++ b/daemon/crypto/constants.hpp @@ -8,17 +8,16 @@ #include "sodium.h" // MAC bytes -constexpr size_t CRYPTO_ABYTES = crypto_aead_xchacha20poly1305_ietf_ABYTES; +constexpr int CRYPTO_ABYTES = crypto_aead_xchacha20poly1305_ietf_ABYTES; // nonce bytes -constexpr size_t CRYPTO_NPUBBYTES = - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; +constexpr int CRYPTO_NPUBBYTES = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; // the maximum size of a message such that it can be sent in a single message // if the message is this size or shorter, it is guaranteed to be sent in a // single round. 1+5 is for the uint32 ID, 1+MESSAGE_SIZE is for the header of // the string, and 1 + 5 is for num_chunks and 1 + 5 is for // chunks_start_sequence_number element. -1 at the end is for the padding which // reserves one byte. -constexpr size_t GUARANTEED_SINGLE_MESSAGE_SIZE = +constexpr int GUARANTEED_SINGLE_MESSAGE_SIZE = MESSAGE_SIZE - (1 + 5) - (1 + CEIL_DIV((sizeof MESSAGE_SIZE) * 8 - std::countl_zero(MESSAGE_SIZE), 8)) - @@ -26,17 +25,17 @@ constexpr size_t GUARANTEED_SINGLE_MESSAGE_SIZE = // we support up to 4 billion messages! that's a lot. // (we use unsigned integers) -constexpr size_t ACKING_BYTES = 4; +constexpr int ACKING_BYTES = 4; // the encryption takes a nonce + a mac -constexpr size_t ENCRYPTED_ACKING_BYTES = +constexpr int ENCRYPTED_ACKING_BYTES = ACKING_BYTES + CRYPTO_ABYTES + CRYPTO_NPUBBYTES; // the maximum number of friends! -constexpr size_t MAX_FRIENDS = MESSAGE_SIZE / ENCRYPTED_ACKING_BYTES; +constexpr int MAX_FRIENDS = MESSAGE_SIZE / ENCRYPTED_ACKING_BYTES; -constexpr size_t MAX_ASYNC_FRIEND_REQUESTS = 500; -constexpr size_t ASYNC_FRIEND_REQUEST_BATCH_SIZE = 1000; +constexpr int MAX_ASYNC_FRIEND_REQUESTS = 500; +constexpr int ASYNC_FRIEND_REQUEST_BATCH_SIZE = 1000; // TODO: figure out a reasonable limit here... -constexpr size_t INVITATION_MESSAGE_MAX_PLAINTEXT_SIZE = 500; +constexpr int INVITATION_MESSAGE_MAX_PLAINTEXT_SIZE = 500; // NOTE: whenever these default values are changed, please make a database // migration in the shape of UPDATE config SET value = 'new_value' WHERE value = diff --git a/daemon/db/db.rs b/daemon/db/db.rs index a9b8770b..c8679461 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -51,8 +51,35 @@ impl fmt::Display for DbError { } // keep this in sync with message.proto -pub enum SystemMessage { - OutgoingInvitation = 0, + +impl Queryable for ffi::SystemMessage +where + DB: diesel::backend::Backend, + i32: diesel::deserialize::FromSql, +{ + type Row = i32; + fn build(s: i32) -> diesel::deserialize::Result { + match s { + 0 => Ok(ffi::SystemMessage::OutgoingInvitation), + _ => Err("invalid system message".into()), + } + } +} + +impl ToSql for ffi::SystemMessage +where + i32: ToSql, +{ + fn to_sql<'b>( + &'b self, + out: &mut Output<'b, '_, diesel::sqlite::Sqlite>, + ) -> diesel::serialize::Result { + match *self { + ffi::SystemMessage::OutgoingInvitation => out.set_value(0 as i32), + _ => return Err("invalid system message".into()), + } + Ok(diesel::serialize::IsNull::No) + } } // TODO(arvid): manage a connection pool for the DB here? @@ -308,6 +335,12 @@ pub mod ffi { pub content: String, } + #[derive(Debug, AsExpression)] + #[diesel(sql_type = diesel::sql_types::Integer)] + enum SystemMessage { + OutgoingInvitation, + } + #[derive(Queryable)] struct OutgoingChunkPlusPlus { pub to_friend: i32, @@ -317,8 +350,9 @@ pub mod ffi { pub content: String, pub write_key: Vec, pub num_chunks: i32, - pub control: bool, - pub control_message: i32, + pub system: bool, + #[diesel(deserialize_as = SystemMessage)] + pub system_message: SystemMessage, } #[derive(Queryable)] @@ -485,7 +519,8 @@ pub mod ffi { // 1. If the friend is marked as deleted, then we ignore the request. // 2. If the friend is marked as accepted, then we ignore the request. // 3. If the friend is marked as incoming, then we update the message. - // 4. If the friend is marked as outgoing, then we approve this request. + // 4. If the friend is marked as outgoing async, then we approve this request. + // (if async outgoing, we just won't know, so we cannot take any special action) // immediately. fn add_incoming_async_invitation(&self, public_id: &str, message: &str) -> Result<()>; // get invitations @@ -522,10 +557,13 @@ pub mod ffi { num_chunks: i32, ) -> Result; // receive the system message telling us to add the friend + fn receive_invitation_system_message( &self, from_friend: i32, sequence_number: i32, + public_id: &str, // we want this public_id to correspond to the one we already have + public_id_claimed_kx_public_key: Vec, // we want to verify that this is the same as we have on file! otherwise someone might be trying to deceive us ) -> Result<()>; // fails if there is no chunk to send @@ -905,33 +943,17 @@ impl DB { pub fn receive_ack(&self, uid: i32, ack: i32) -> Result { let mut conn = self.connect()?; - use crate::schema::friend; use crate::schema::outgoing_chunk; use crate::schema::sent; - use crate::schema::status; + use crate::schema::transmission; let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { let old_acked_seqnum = - status::table.find(uid).select(status::sent_acked_seqnum).first(conn_b)?; + transmission::table.find(uid).select(transmission::sent_acked_seqnum).first(conn_b)?; if ack > old_acked_seqnum { - diesel::update(status::table.find(uid)) - .set(status::sent_acked_seqnum.eq(ack)) + diesel::update(transmission::table.find(uid)) + .set(transmission::sent_acked_seqnum.eq(ack)) .execute(conn_b)?; - // check if there are any control messages that were ACKed - let control_chunks = outgoing_chunk::table - .filter(outgoing_chunk::to_friend.eq(uid)) - .filter(outgoing_chunk::sequence_number.le(ack)) - .filter(outgoing_chunk::control.eq(true)) - .select(outgoing_chunk::control_message) - .load::(conn_b)?; - for control_message in control_chunks { - if control_message == SystemMessage::OutgoingInvitation { - // yay! they shall now be considered a Real Friend - diesel::update(friend::table.find(uid)) - .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) - .execute(conn_b)?; - } - } // delete all outgoing chunks with seqnum <= ack diesel::delete( outgoing_chunk::table @@ -979,11 +1001,13 @@ impl DB { from_friend: i32, sequence_number: i32, ) -> Result { - use crate::schema::status; + use crate::schema::transmission; conn.transaction::<_, diesel::result::Error, _>(|conn_b| { - let old_seqnum = - status::table.find(from_friend).select(status::received_seqnum).first::(conn_b)?; + let old_seqnum = transmission::table + .find(from_friend) + .select(transmission::received_seqnum) + .first::(conn_b)?; // if chunk is before old_seqnum, we just ignore it!! // in other words, once a chunk has been received, it can never be patched. // this is good. if something bad happens, you can always retransmit in a new @@ -995,8 +1019,8 @@ impl DB { // so we only update the seqnum if we increase exactly by one (otherwise we might miss messages!) if sequence_number == old_seqnum + 1 { - diesel::update(status::table.find(from_friend)) - .set(status::received_seqnum.eq(sequence_number)) + diesel::update(transmission::table.find(from_friend)) + .set(transmission::received_seqnum.eq(sequence_number)) .execute(conn_b)?; } Ok(ffi::ReceiveChunkStatus::NewChunk) @@ -1110,10 +1134,12 @@ impl DB { } } - fn receive_invitation_system_message( + pub fn receive_invitation_system_message( &self, from_friend: i32, sequence_number: i32, + public_id: &str, + public_id_claimed_kx_public_key: Vec, ) -> Result<(), DbError> { let mut conn = self.connect()?; use crate::schema::friend; @@ -1145,10 +1171,10 @@ impl DB { ) -> Result { let mut conn = self.connect()?; - use crate::schema::address; use crate::schema::friend; use crate::schema::outgoing_chunk; use crate::schema::sent; + use crate::schema::transmission; // We could do probably this in one query, by joining on the select statement // and then joining. Diesel doesn't typecheck this, and maybe it is unsafe, so @@ -1181,7 +1207,7 @@ impl DB { })(); let chunk_plusplus = outgoing_chunk::table .find(chosen_chunk) - .inner_join(friend::table.inner_join(address::table)) + .inner_join(friend::table.inner_join(transmission::table)) .inner_join(sent::table) .select(( outgoing_chunk::to_friend, @@ -1189,10 +1215,10 @@ impl DB { outgoing_chunk::chunks_start_sequence_number, outgoing_chunk::message_uid, outgoing_chunk::content, - address::write_key, + transmission::write_key, sent::num_chunks, - outgoing_chunk::control, - outgoing_chunk::control_message, + outgoing_chunk::system, + outgoing_chunk::system_message, )) .first::(conn_b)?; Ok(chunk_plusplus) @@ -1206,7 +1232,6 @@ impl DB { pub fn acks_to_send(&self) -> Result, DbError> { let mut conn = self.connect()?; - use crate::schema::address; use crate::schema::friend; use crate::schema::transmission; let wide_friends = @@ -1227,9 +1252,19 @@ impl DB { pub fn get_friend_address(&self, uid: i32) -> Result { let mut conn = self.connect()?; - use crate::schema::address; + use crate::schema::transmission; - match address::table.find(uid).first(&mut conn) { + match transmission::table + .find(uid) + .select(( + transmission::friend_uid, + transmission::read_index, + transmission::read_key, + transmission::write_key, + transmission::ack_index, + )) + .first(&mut conn) + { Ok(address) => Ok(address), Err(e) => Err(DbError::Unknown(format!("get_friend_address: {}", e))), } @@ -1240,15 +1275,21 @@ impl DB { uids: Vec, ) -> Result { let mut conn = self.connect()?; - use crate::schema::address; use crate::schema::friend; + use crate::schema::transmission; // get a random friend that is not deleted excluding the ones in the uids list // we alow getting any outgoing friend too. they should be treated normally let q = friend::table.filter(friend::deleted.eq(false)); // Inner join to get the (friend, address) pairs and then select the address. - let q = q.inner_join(address::table).select(address::all_columns); + let q = q.inner_join(transmission::table).select(( + transmission::friend_uid, + transmission::read_index, + transmission::read_key, + transmission::write_key, + transmission::ack_index, + )); let addresses = q.load::(&mut conn); match addresses { @@ -1296,7 +1337,7 @@ impl DB { use crate::schema::message; use crate::schema::outgoing_chunk; use crate::schema::sent; - use crate::schema::status; + use crate::schema::transmission; conn .transaction::<_, diesel::result::Error, _>(|conn_b| { @@ -1327,9 +1368,10 @@ impl DB { .limit(1) .load::(conn_b)?; let old_seqnum = match maybe_old_seqnum.len() { - 0 => { - status::table.find(friend_uid).select(status::sent_acked_seqnum).first::(conn_b)? - } + 0 => transmission::table + .find(friend_uid) + .select(transmission::sent_acked_seqnum) + .first::(conn_b)?, _ => maybe_old_seqnum[0], }; let new_seqnum = old_seqnum + 1; @@ -1342,8 +1384,8 @@ impl DB { outgoing_chunk::chunks_start_sequence_number.eq(new_seqnum), outgoing_chunk::message_uid.eq(message_uid), outgoing_chunk::content.eq(chunk), - outgoing_chunk::control.eq(false), - outgoing_chunk::control_message.eq(0), // doesn't matter, we don't use this if control is false anyway + outgoing_chunk::system.eq(false), + outgoing_chunk::system_message.eq(ffi::SystemMessage::OutgoingInvitation), // doesn't matter, we don't use this if control is false anyway )) .execute(conn_b)?; } diff --git a/daemon/db/schema.rs b/daemon/db/schema.rs index f0ec0af9..35f7b5fe 100644 --- a/daemon/db/schema.rs +++ b/daemon/db/schema.rs @@ -48,8 +48,7 @@ diesel::table! { } diesel::table! { - incoming_invitation (friend_uid) { - friend_uid -> Integer, + incoming_invitation (public_id) { public_id -> Text, message -> Text, received_at -> BigInt, @@ -81,8 +80,8 @@ diesel::table! { chunks_start_sequence_number -> Integer, message_uid -> Integer, content -> Text, - control -> Bool, - control_message -> Integer, + system -> Bool, + system_message -> Integer, } } @@ -139,7 +138,7 @@ diesel::table! { read_index -> Integer, read_key -> Binary, write_key -> Binary, - ack_index -> Nullable, + ack_index -> Integer, sent_acked_seqnum -> Integer, received_seqnum -> Integer, } @@ -151,7 +150,6 @@ diesel::joinable!(draft -> friend (to_friend)); diesel::joinable!(draft -> message (uid)); diesel::joinable!(incoming_chunk -> friend (from_friend)); diesel::joinable!(incoming_chunk -> received (message_uid)); -diesel::joinable!(incoming_invitation -> friend (friend_uid)); diesel::joinable!(outgoing_async_invitation -> friend (friend_uid)); diesel::joinable!(outgoing_chunk -> friend (to_friend)); diesel::joinable!(outgoing_chunk -> sent (message_uid)); diff --git a/daemon/db/tests.rs b/daemon/db/tests.rs index 0ad3b78a..a7531f98 100644 --- a/daemon/db/tests.rs +++ b/daemon/db/tests.rs @@ -111,20 +111,23 @@ fn test_receive_msg() { let config_data = get_registration_fragment(); db.do_register(config_data).unwrap(); - let f = db.create_friend("friend_1", "Friend 1", "tttt", 20).unwrap(); - db.add_friend_address( - ffi::AddAddress { - unique_name: "friend_1".to_string(), - kx_public_key: br#"uuuu"#.to_vec(), - friend_request_public_key: br#"vvvv"#.to_vec(), - friend_request_message: "hello".to_string(), - read_index: 0, - read_key: br#"xxxx"#.to_vec(), - write_key: br#"wwww"#.to_vec(), - }, - 20, - ) - .unwrap(); + // add friend by issuing an outgoing invitation and then accepting an incoming invitation + let f = db + .add_outgoing_async_invitation( + "friend_1", + "Friend 1", + "hi_this_is_a_public_id", + br#"fffff"#.to_vec(), + br#"kxkxkx"#.to_vec(), + "message hi hi", + 0, + br#"rrrrr"#.to_vec(), + br#"wwww"#.to_vec(), + 20, + ) + .unwrap(); + // will be auto accepted! + db.add_incoming_async_invitation("hi_this_is_a_public_id", "hi from freidn 1").unwrap(); let msg = "hi im a chunk"; let chunk_status = db @@ -155,10 +158,10 @@ fn test_receive_msg() { assert_eq!(msgs[0].content, msg); let mut conn = SqliteConnection::establish(&db.address).unwrap(); - use crate::schema::status; - let status_pair = status::table + use crate::schema::transmission; + let status_pair = transmission::table .find(msgs[0].uid) - .select((status::sent_acked_seqnum, status::received_seqnum)) + .select((transmission::sent_acked_seqnum, transmission::received_seqnum)) .first::<(i32, i32)>(&mut conn) .unwrap(); assert_eq!(status_pair.0, 0); @@ -180,20 +183,22 @@ fn test_send_msg() { let config_data = get_registration_fragment(); db.do_register(config_data).unwrap(); - let f = db.create_friend("friend_1", "Friend 1", "tttt", 20).unwrap(); - db.add_friend_address( - ffi::AddAddress { - unique_name: "friend_1".to_string(), - kx_public_key: br#"uuuu"#.to_vec(), - friend_request_public_key: br#"vvvv"#.to_vec(), - friend_request_message: "hello".to_string(), - read_index: 0, - read_key: br#"xxxx"#.to_vec(), - write_key: br#"wwww"#.to_vec(), - }, - 20, - ) - .unwrap(); + let f = db + .add_outgoing_async_invitation( + "friend_1", + "Friend 1", + "hi_this_is_a_public_id", + br#"fffff"#.to_vec(), + br#"kxkxkx"#.to_vec(), + "message hi hi", + 0, + br#"rrrrr"#.to_vec(), + br#"wwww"#.to_vec(), + 20, + ) + .unwrap(); + // will be auto accepted! + db.add_incoming_async_invitation("hi_this_is_a_public_id", "hi from freidn 1").unwrap(); let msg = "hi im a single chunk"; db.queue_message_to_send("friend_1", msg, vec![msg.to_string()]).unwrap(); @@ -224,23 +229,7 @@ fn test_async_add_friend() { assert!(db.get_incoming_invitations().unwrap().is_empty()); // add an incoming friend request let friend_name = "friend_1"; - let friend_request = ffi::FriendFragment { - unique_name: friend_name.to_string(), - display_name: "lyrica".to_string(), - public_id: "tttt".to_string(), - request_progress: ffi::FriendRequestProgress::Incoming, - deleted: false, - }; - - let address = ffi::AddAddress { - unique_name: friend_name.to_string(), - read_index: 0, - read_key: br#"xxxx"#.to_vec(), - write_key: br#"wwww"#.to_vec(), - kx_public_key: br#"xxxx"#.to_vec(), - friend_request_public_key: br#"xxxx"#.to_vec(), - friend_request_message: "finally made a friend".to_string(), - }; + db.add_incoming_async_invitation("fake_public_id_string", "hi! do you want to be my friend?") .unwrap(); let friend_requests = db.get_incoming_invitations().unwrap(); diff --git a/daemon/migrations/2022-06-16-052955_create_initial_schema/down.sql b/daemon/migrations/2022-06-16-052955_create_initial_schema/down.sql index d2abea90..bd58dc19 100644 --- a/daemon/migrations/2022-06-16-052955_create_initial_schema/down.sql +++ b/daemon/migrations/2022-06-16-052955_create_initial_schema/down.sql @@ -1,8 +1,11 @@ DROP TABLE config; DROP TABLE registration; +DROP TABLE incoming_invitation; DROP TABLE friend; -DROP TABLE address; -DROP TABLE status; +DROP TABLE outgoing_sync_invitation; +DROP TABLE outgoing_async_invitation; +DROP TABLE complete_friend; +DROP TABLE transmission; DROP TABLE message; DROP TABLE draft; diff --git a/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql b/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql index 6601eb20..67d6bcd3 100644 --- a/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql +++ b/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql @@ -30,7 +30,7 @@ CREATE TABLE registration ( CREATE TABLE incoming_invitation ( public_id text PRIMARY KEY NOT NULL, message text NOT NULL, - received_at timestamp NOT NULL, + received_at timestamp NOT NULL ); -- never delete a friend! instead, set `deleted` to true, or else we will lose history! @@ -85,7 +85,7 @@ CREATE TABLE transmission ( -- ack_index is the index into the acking data for this friend -- this NEEDS to be unique for every friend!! -- This needs to be between 0 <= ack_index < MAX_FRIENDS - ack_index integer, -- in the case of an incoming request, this will be NULL + ack_index integer NOT NULL, -- sent_acked_seqnum is the latest sequence number that was ACKed by the friend -- any message with seqnum > sent_acked_seqnum MUST be retried. sent_acked_seqnum integer NOT NULL, @@ -139,8 +139,8 @@ CREATE TABLE outgoing_chunk ( chunks_start_sequence_number integer NOT NULL, message_uid integer NOT NULL, content text NOT NULL, - control boolean NOT NULL, - control_message integer NOT NULL, -- corresponds to the enum value in the protobuf + system boolean NOT NULL, + system_message integer NOT NULL, -- corresponds to the enum value in the protobuf PRIMARY KEY (to_friend, sequence_number), FOREIGN KEY(message_uid) REFERENCES sent(uid), FOREIGN KEY(to_friend) REFERENCES friend(uid) diff --git a/daemon/transmitter/transmitter.cc b/daemon/transmitter/transmitter.cc index 79a9b0d1..42e225b3 100644 --- a/daemon/transmitter/transmitter.cc +++ b/daemon/transmitter/transmitter.cc @@ -7,6 +7,7 @@ #include +#include "daemon/identifier/identifier.hpp" #include "schema/server.grpc.pb.h" auto generate_dummy_address(const db::Registration& reg) -> db::Address { @@ -29,23 +30,29 @@ auto generate_dummy_address(const db::Registration& reg) -> db::Address { auto generate_dummy_async_invitation() -> db::OutgoingAsyncInvitation { auto dummy_kx_keypair = crypto::generate_kx_keypair(); - auto kx_public_key = rust_u8Vec_to_string(dummy_kx_keypair.first); + auto kx_public_key = dummy_kx_keypair.first; + auto kx_public_key_rust = string_to_rust_u8Vec(kx_public_key); auto dummy_friend_request_keypair = crypto::generate_friend_request_keypair(); - auto friend_request_public_key = - rust_u8Vec_to_string(dummy_friend_request_keypair.first); + auto friend_request_public_key = dummy_friend_request_keypair.first; + auto friend_request_public_key_rust = + string_to_rust_u8Vec(friend_request_public_key); auto public_id = PublicIdentifier(0, kx_public_key, friend_request_public_key); auto public_id_str = public_id.to_public_id(); - return db::OutgoingAsyncInvitation { - .friend_uid = -1, .unique_name = "dummy", .display_name = "Dummy", - .invitation_progress = db::InvitationProgress::OutgoingAsync, - .public_id = public_id_str, - .friend_request_public_key = friend_request_public_key, - .kx_public_key = kx_public_key, .message = "Hello dummy", .sent_at = 0, - } + return db::OutgoingAsyncInvitation{ + .friend_uid = -1, + .unique_name = "dummy", + .display_name = "Dummy", + .invitation_progress = db::InvitationProgress::OutgoingAsync, + .public_id = public_id_str, + .friend_request_public_key = friend_request_public_key_rust, + .kx_public_key = kx_public_key_rust, + .message = "Hello dummy", + .sent_at = 0, + }; } Transmitter::Transmitter(Global& G, shared_ptr stub) @@ -67,8 +74,8 @@ auto Transmitter::setup_registration_caching() -> void { rust_u8Vec_to_string(reg.pir_galois_key))); cached_pir_client_secret_key = rust_u8Vec_to_string(reg.pir_secret_key); - dummy_address = generate_dummy_address(G, reg); - dummy_outgoing_invitation = generate_dummy_async_invitation(G, reg); + dummy_address = generate_dummy_address(reg); + dummy_outgoing_invitation = generate_dummy_async_invitation(); } check_rep(); @@ -289,25 +296,49 @@ auto Transmitter::retrieve() -> void { chunks_start_sequence_number, chunk.chunks_start_sequence_number(), num_chunks, chunk.num_chunks(), chunk_content, chunk.msg(), - control, chunk.control(), control_message, - chunk.control_message()); + is_system, chunk.system(), system_message, + chunk.system_message(), system_message_data, + chunk.system_message_data()); } - if (chunk.control()) { - switch (chunk.control_message()) { - case asphrclient::Message::OUTGOING_FRIEND_REQUEST: + if (chunk.system()) { + switch (chunk.system_message()) { + case asphrclient::SystemMessage::OUTGOING_INVITATION: { ASPHR_LOG_INFO( - "Received outgoing friend request from someone who's already " - "someone we wanted to add.", + "Received outgoing invitation from someone who's already " + "someone we wanted to add. This means we want to make them a " + "complete friend!!", friend_uid, f.uid); - G.db->receive_friend_request_control_message( - f.uid, chunk.sequence_number()); + // we need to get the public_id here, and verify that it is + // correct with what we have on file! + auto public_id_status = + PublicIdentifier::from_public_id(chunk.system_message_data()); + if (!public_id_status.ok()) { + ASPHR_LOG_ERR( + "Received outgoing invitation from someone who's already " + "someone we wanted to add, but the public id was invalid.", + friend_uid, f.uid); + } else { + auto public_id = public_id_status.value(); + try { + G.db->receive_invitation_system_message( + f.uid, chunk.sequence_number(), + chunk.system_message_data(), + string_to_rust_u8Vec(public_id.kx_public_key)); + } catch (const rust::Error& e) { + ASPHR_LOG_ERR("Unable to receive invitation system message", + error_message, e.what(), friend_uid, f.uid); + } + } break; - default: - ASPHR_LOG_ERR("Received unknown control message from friend.", - friend_uid, f.uid, control_message, - chunk.control_message()); + } + default: { + ASPHR_LOG_ERR("Received unknown system message from friend.", + friend_uid, f.uid, system_message, + chunk.system_message(), system_message_data, + chunk.system_message_data()); ASPHR_ASSERT(false); + } } } else { // we don't set these fields if we only have one chunk @@ -522,7 +553,10 @@ auto Transmitter::transmit_async_invitation() -> void { // retrieve the friend request from DB std::vector invitations = {}; try { - invitations = G.db->get_outgoing_async_invitations(); + auto rust_invitations = G.db->get_outgoing_async_invitations(); + for (auto& rust_invitation : rust_invitations) { + invitations.push_back(rust_invitation); + } } catch (const rust::Error& e) { ASPHR_LOG_ERR("Failed to get outgoing async invitations.", error_msg, e.what()); @@ -537,7 +571,7 @@ auto Transmitter::transmit_async_invitation() -> void { } ASPHR_ASSERT_EQ_MSG( - invitations.size(), 1, + std::ssize(invitations), 1, "We only support one outgoing invitation at once at the moment."); // send the friend request @@ -564,7 +598,7 @@ auto Transmitter::transmit_async_invitation() -> void { // encrypt the friend request auto encrypted_friend_request_status_ = crypto::encrypt_async_friend_request( my_id, my_friend_request_private_key, friend_id, - std::string(invitation.friend_request_message)); + std::string(invitation.message)); if (!encrypted_friend_request_status_.ok()) { ASPHR_LOG_ERR("Error encrypting async friend request: ", error_msg, @@ -671,8 +705,8 @@ auto Transmitter::retrieve_async_invitations(int start_index, int end_index) // Step 2.2: unpack the friend request auto [friend_public_id_str, friend_message] = decrypted_friend_request_status.value(); - ASPHR_LOG_INFO("Found async friend request!", public_id, friend_id, message, - friend_message); + ASPHR_LOG_INFO("Found async friend request!", public_id, + friend_public_id_str, message, friend_message); // unpack the friend id auto friend_public_id_status = PublicIdentifier::from_public_id(friend_public_id_str); From c0b1804fd06728a770ab09513aaafd40b079a769 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 02:47:59 +0000 Subject: [PATCH 06/22] =?UTF-8?q?=F0=9F=94=84=20"it=20builds=20"=20Update?= =?UTF-8?q?=20anysphere/asphr=20commit=20SHA=20=F0=9F=94=97=20https://gith?= =?UTF-8?q?ub.com/anysphere/asphr/commit/209b037166f2d9bc4de62daf80a00ea1f?= =?UTF-8?q?0cde0fd?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WORKSPACE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKSPACE b/WORKSPACE index de33ca76..c20d2d3a 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -7,7 +7,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( name = "asphr", - commit = "4415feb0985123d7031924f7298f9b78b3db058f", # autoupdate anysphere/asphr + commit = "209b037166f2d9bc4de62daf80a00ea1f0cde0fd", # autoupdate anysphere/asphr init_submodules = True, remote = "https://github.com/anysphere/asphr.git", ) From 487cdef9664c8008b1601219cc4ba248861d19cf Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 22:26:03 -0700 Subject: [PATCH 07/22] it builds! --- daemon/db/db.rs | 189 +++++++++++++----- daemon/db/schema.rs | 2 +- .../up.sql | 2 +- daemon/transmitter/transmitter.cc | 24 ++- 4 files changed, 156 insertions(+), 61 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index c8679461..7f12c186 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -141,6 +141,27 @@ where } } +pub struct I32ButMinusOneIsNone(i32); + +impl From for i32 { + fn from(x: I32ButMinusOneIsNone) -> i32 { + x.0 + } +} + +impl Queryable, DB> + for I32ButMinusOneIsNone +where + DB: diesel::backend::Backend, + Option: + diesel::deserialize::FromSql, DB>, +{ + type Row = Option; + fn build(s: Option) -> diesel::deserialize::Result { + Ok(I32ButMinusOneIsNone(s.unwrap_or(-1))) + } +} + impl Queryable for ffi::InvitationProgress where DB: diesel::backend::Backend, @@ -346,7 +367,8 @@ pub mod ffi { pub to_friend: i32, pub sequence_number: i32, pub chunks_start_sequence_number: i32, - pub message_uid: i32, + #[diesel(deserialize_as = crate::db::I32ButMinusOneIsNone)] + pub message_uid: i32, // -1 iff system message pub content: String, pub write_key: Vec, pub num_chunks: i32, @@ -1322,6 +1344,35 @@ impl DB { q.first(conn) } + fn get_seqnum_for_new_chunk( + &self, + conn: &mut SqliteConnection, + friend_uid: i32, + ) -> Result { + use crate::schema::outgoing_chunk; + use crate::schema::transmission; + conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + // get the correct sequence number. This is the last sequence number + 1. + // this is either the maximum sequence number in the table + 1, + // or the sent_acked_seqnum + 1 if there are no outgoing chunks. + let maybe_old_seqnum = outgoing_chunk::table + .filter(outgoing_chunk::to_friend.eq(friend_uid)) + .select(outgoing_chunk::sequence_number) + .order_by(outgoing_chunk::sequence_number.desc()) + .limit(1) + .load::(conn_b)?; + let old_seqnum = match maybe_old_seqnum.len() { + 0 => transmission::table + .find(friend_uid) + .select(transmission::sent_acked_seqnum) + .first::(conn_b)?, + _ => maybe_old_seqnum[0], + }; + let new_seqnum = old_seqnum + 1; + Ok(new_seqnum) + }) + } + pub fn queue_message_to_send( &self, to_unique_name: &str, @@ -1337,7 +1388,6 @@ impl DB { use crate::schema::message; use crate::schema::outgoing_chunk; use crate::schema::sent; - use crate::schema::transmission; conn .transaction::<_, diesel::result::Error, _>(|conn_b| { @@ -1358,23 +1408,7 @@ impl DB { )) .execute(conn_b)?; - // get the correct sequence number. This is the last sequence number + 1. - // this is either the maximum sequence number in the table + 1, - // or the sent_acked_seqnum + 1 if there are no outgoing chunks. - let maybe_old_seqnum = outgoing_chunk::table - .filter(outgoing_chunk::to_friend.eq(friend_uid)) - .select(outgoing_chunk::sequence_number) - .order_by(outgoing_chunk::sequence_number.desc()) - .limit(1) - .load::(conn_b)?; - let old_seqnum = match maybe_old_seqnum.len() { - 0 => transmission::table - .find(friend_uid) - .select(transmission::sent_acked_seqnum) - .first::(conn_b)?, - _ => maybe_old_seqnum[0], - }; - let new_seqnum = old_seqnum + 1; + let new_seqnum = self.get_seqnum_for_new_chunk(conn_b, friend_uid)?; for (i, chunk) in chunks.iter().enumerate() { diesel::insert_into(outgoing_chunk::table) @@ -1654,7 +1688,7 @@ impl DB { //////////////////////////////////////////////////////////////////////////// pub fn has_space_for_async_invitations(&self) -> Result { - // return true iff no async friend requests are in the database + // return true iff no async friend requests are in the database, because we only allow 1 outgoing right now // TODO: update the limit to allow more outgoing async requests if let Ok(f) = self.get_outgoing_async_invitations() { if f.len() == 0 { @@ -1667,6 +1701,12 @@ impl DB { } } + fn get_public_id(&self, conn: &mut SqliteConnection) -> Result { + use crate::schema::registration; + let q = registration::table.select(registration::public_id); + q.first::(conn) + } + pub fn add_outgoing_sync_invitation( &self, unique_name: &str, @@ -1678,42 +1718,83 @@ impl DB { write_key: Vec, max_friends: i32, ) -> Result { - return Err(DbError::Unimplemented( - "add_outgoing_sync_invitation CHECK BELOW IMPL FOR NEW INERFACE".to_string(), - )); - // let mut conn = self.connect()?; - // use crate::schema::friend; + // 1. Verify that the user has space for another friend + // 2. Verify no other friend with same unique_name + // 3. Get public ID and chunk sequence number + // 4. Insert friend into database + // 5. Insert outgoing invitation into database + // 6. Insert outgoing chunk into database + // that's it :) + let mut conn = self.connect()?; + use crate::schema::friend; + use crate::schema::outgoing_chunk; + use crate::schema::outgoing_sync_invitation; - // let f = ffi::FriendFragment { - // unique_name: unique_name.to_string(), - // display_name: display_name.to_string(), - // public_id: public_id.to_string(), - // request_progress: ffi::InvitationProgress::Complete, - // deleted: false, - // }; - // let r = conn.transaction(|conn_b| { - // // check if a friend with this name already exists - // let count = friend::table - // .filter(friend::unique_name.eq(unique_name)) - // .count() - // .get_result::(conn_b)?; - // if count > 0 { - // return Err(diesel::result::Error::RollbackTransaction); - // } - // let count = friend::table.count().get_result::(conn_b)?; - // if count >= max_friends.into() { - // return Err(diesel::result::Error::RollbackTransaction); - // } - - // diesel::insert_into(friend::table).values(&f).get_result::(conn_b) - // }); + let friend_fragment = ffi::FriendFragment { + unique_name: unique_name.to_string(), + display_name: display_name.to_string(), + invitation_progress: ffi::InvitationProgress::OutgoingSync, + deleted: false, + }; + let r = conn.transaction(|conn_b| { + // check if a friend with this name already exists + let count = friend::table + .filter(friend::unique_name.eq(unique_name)) + .count() + .get_result::(conn_b)?; + if count > 0 { + return Err(diesel::result::Error::RollbackTransaction); + } + let count = friend::table.count().get_result::(conn_b)?; + if count >= max_friends.into() { + return Err(diesel::result::Error::RollbackTransaction); + } - // r.map_err(|e| match e { - // diesel::result::Error::RollbackTransaction => { - // DbError::AlreadyExists("friend already exists, or too many friends".to_string()) - // } - // _ => DbError::Unknown(format!("failed to insert friend: {}", e)), - // }) + let friend = diesel::insert_into(friend::table) + .values(&friend_fragment) + .returning(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + friend::deleted, + )) + .get_result::(conn_b)?; + + diesel::insert_into(outgoing_sync_invitation::table) + .values(( + outgoing_sync_invitation::friend_uid.eq(friend.uid), + outgoing_sync_invitation::story.eq(story.to_string()), + outgoing_sync_invitation::kx_public_key.eq(kx_public_key), + outgoing_sync_invitation::sent_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + + let public_id = self.get_public_id(conn_b)?; + + let new_seqnum = self.get_seqnum_for_new_chunk(conn_b, friend.uid)?; + + diesel::insert_into(outgoing_chunk::table) + .values(( + outgoing_chunk::to_friend.eq(friend.uid), + outgoing_chunk::sequence_number.eq(new_seqnum), + outgoing_chunk::chunks_start_sequence_number.eq(new_seqnum), + outgoing_chunk::message_uid.eq(-1), + outgoing_chunk::content.eq(public_id), // the content is public_id + outgoing_chunk::system.eq(true), + outgoing_chunk::system_message.eq(ffi::SystemMessage::OutgoingInvitation), + )) + .execute(conn_b)?; + + Ok(friend) + }); + + r.map_err(|e| match e { + diesel::result::Error::RollbackTransaction => { + DbError::AlreadyExists("friend already exists, or too many friends".to_string()) + } + _ => DbError::Unknown(format!("failed to insert friend: {}", e)), + }) } pub fn add_outgoing_async_invitation( diff --git a/daemon/db/schema.rs b/daemon/db/schema.rs index 35f7b5fe..cb689f80 100644 --- a/daemon/db/schema.rs +++ b/daemon/db/schema.rs @@ -78,7 +78,7 @@ diesel::table! { to_friend -> Integer, sequence_number -> Integer, chunks_start_sequence_number -> Integer, - message_uid -> Integer, + message_uid -> Nullable, content -> Text, system -> Bool, system_message -> Integer, diff --git a/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql b/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql index 67d6bcd3..13f8abd0 100644 --- a/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql +++ b/daemon/migrations/2022-06-16-052955_create_initial_schema/up.sql @@ -137,7 +137,7 @@ CREATE TABLE outgoing_chunk ( to_friend integer NOT NULL, sequence_number integer NOT NULL, chunks_start_sequence_number integer NOT NULL, - message_uid integer NOT NULL, + message_uid integer, -- null iff system message content text NOT NULL, system boolean NOT NULL, system_message integer NOT NULL, -- corresponds to the enum value in the protobuf diff --git a/daemon/transmitter/transmitter.cc b/daemon/transmitter/transmitter.cc index 42e225b3..488b0def 100644 --- a/daemon/transmitter/transmitter.cc +++ b/daemon/transmitter/transmitter.cc @@ -426,12 +426,26 @@ auto Transmitter::send() -> void { write_key = rust_u8Vec_to_string(chunk_to_send.write_key); message.set_sequence_number(chunk_to_send.sequence_number); - message.set_msg(std::string(chunk_to_send.content)); - if (chunk_to_send.num_chunks > 1) { - message.set_num_chunks(chunk_to_send.num_chunks); - message.set_chunks_start_sequence_number( - chunk_to_send.chunks_start_sequence_number); + + if (chunk_to_send.system) { + message.set_system(true); + switch (chunk_to_send.system_message) { + case db::SystemMessage::OutgoingInvitation: + message.set_system_message(asphrclient::OUTGOING_INVITATION); + break; + default: + ASPHR_ASSERT(false); + } + message.set_system_message_data(std::string(chunk_to_send.content)); + } else { + message.set_msg(std::string(chunk_to_send.content)); + if (chunk_to_send.num_chunks > 1) { + message.set_num_chunks(chunk_to_send.num_chunks); + message.set_chunks_start_sequence_number( + chunk_to_send.chunks_start_sequence_number); + } } + } catch (const rust::Error& e) { ASPHR_LOG_INFO("No chunks to send (probably).", error_msg, e.what()); just_sent_friend = std::nullopt; From 1c0940f96350fc1ac2f151d423106bbfe627b3b8 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 23:01:45 -0700 Subject: [PATCH 08/22] it builds! --- daemon/db/db.rs | 255 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 193 insertions(+), 62 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 7f12c186..43543f78 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -260,14 +260,6 @@ pub mod ffi { pub write_key: Vec, pub ack_index: i32, } - #[derive(Insertable)] - #[diesel(table_name = crate::schema::transmission)] - struct AddressFragment { - pub read_index: i32, - pub read_key: Vec, - pub write_key: Vec, - pub ack_index: i32, - } #[derive(Queryable)] struct OutgoingSyncInvitation { pub friend_uid: i32, @@ -1707,6 +1699,99 @@ impl DB { q.first::(conn) } + fn can_add_friend( + &self, + conn: &mut SqliteConnection, + unique_name: &str, + kx_public_key: Vec, + max_friends: i32, + ) -> Result { + use crate::schema::complete_friend; + use crate::schema::friend; + use crate::schema::outgoing_async_invitation; + use crate::schema::outgoing_sync_invitation; + conn.transaction(|conn_b| { + // check if a friend with this name already exists + let count = friend::table + .filter(friend::unique_name.eq(unique_name)) + .count() + .get_result::(conn_b)?; + if count > 0 { + return Ok(false); + } + // check that we have don't have too many friends + let count = friend::table.count().get_result::(conn_b)?; + if count >= max_friends.into() { + return Ok(false); + } + // check that no friend exists with the same kx_public_key + let count_kx = complete_friend::table + .filter(complete_friend::kx_public_key.eq(kx_public_key.clone())) + .count() + .get_result::(conn_b)?; + if count_kx > 0 { + return Ok(false); + } + let count_kx2 = outgoing_async_invitation::table + .filter(outgoing_async_invitation::kx_public_key.eq(kx_public_key.clone())) + .count() + .get_result::(conn_b)?; + if count_kx2 > 0 { + return Ok(false); + } + let count_kx3 = outgoing_sync_invitation::table + .filter(outgoing_sync_invitation::kx_public_key.eq(kx_public_key.clone())) + .count() + .get_result::(conn_b)?; + if count_kx3 > 0 { + return Ok(false); + } + Ok(true) + }) + } + + fn create_transmission_record( + &self, + conn: &mut SqliteConnection, + friend_uid: i32, + read_index: i32, + read_key: Vec, + write_key: Vec, + max_friends: i32, + ) -> Result<(), diesel::result::Error> { + use crate::schema::friend; + use crate::schema::transmission; + + conn.transaction(|conn_b| { + let ack_indices = transmission::table + .inner_join(friend::table) + .filter(friend::deleted.eq(false)) + .select(transmission::ack_index) + .load::(conn_b)?; + let mut possible_ack_indices = Vec::::new(); + for i in 0..max_friends { + if !ack_indices.contains(&i) { + possible_ack_indices.push(i); + } + } + use rand::seq::SliceRandom; + let ack_index_opt = possible_ack_indices.choose(&mut rand::thread_rng()); + let ack_index = ack_index_opt.ok_or(diesel::result::Error::RollbackTransaction)?; + diesel::insert_into(transmission::table) + .values(( + transmission::friend_uid.eq(friend_uid), + transmission::read_index.eq(read_index), + transmission::read_key.eq(read_key), + transmission::write_key.eq(write_key), + transmission::ack_index.eq(ack_index), + transmission::sent_acked_seqnum.eq(0), + transmission::received_seqnum.eq(0), + )) + .execute(conn_b)?; + Ok(()) + }) + } + pub fn add_outgoing_sync_invitation( &self, unique_name: &str, @@ -1724,6 +1809,7 @@ impl DB { // 4. Insert friend into database // 5. Insert outgoing invitation into database // 6. Insert outgoing chunk into database + // 7. Insert transmission information into database // that's it :) let mut conn = self.connect()?; use crate::schema::friend; @@ -1737,16 +1823,8 @@ impl DB { deleted: false, }; let r = conn.transaction(|conn_b| { - // check if a friend with this name already exists - let count = friend::table - .filter(friend::unique_name.eq(unique_name)) - .count() - .get_result::(conn_b)?; - if count > 0 { - return Err(diesel::result::Error::RollbackTransaction); - } - let count = friend::table.count().get_result::(conn_b)?; - if count >= max_friends.into() { + let can_add = self.can_add_friend(conn_b, unique_name, kx_public_key.clone(), max_friends)?; + if !can_add { return Err(diesel::result::Error::RollbackTransaction); } @@ -1765,12 +1843,12 @@ impl DB { .values(( outgoing_sync_invitation::friend_uid.eq(friend.uid), outgoing_sync_invitation::story.eq(story.to_string()), - outgoing_sync_invitation::kx_public_key.eq(kx_public_key), + outgoing_sync_invitation::kx_public_key.eq(kx_public_key.clone()), outgoing_sync_invitation::sent_at.eq(util::unix_micros_now()), )) .execute(conn_b)?; - let public_id = self.get_public_id(conn_b)?; + let my_public_id = self.get_public_id(conn_b)?; let new_seqnum = self.get_seqnum_for_new_chunk(conn_b, friend.uid)?; @@ -1780,20 +1858,31 @@ impl DB { outgoing_chunk::sequence_number.eq(new_seqnum), outgoing_chunk::chunks_start_sequence_number.eq(new_seqnum), outgoing_chunk::message_uid.eq(-1), - outgoing_chunk::content.eq(public_id), // the content is public_id + outgoing_chunk::content.eq(my_public_id), // the content is public_id outgoing_chunk::system.eq(true), outgoing_chunk::system_message.eq(ffi::SystemMessage::OutgoingInvitation), )) .execute(conn_b)?; + self.create_transmission_record( + conn_b, + friend.uid, + read_index, + read_key, + write_key, + max_friends, + )?; + Ok(friend) }); r.map_err(|e| match e { - diesel::result::Error::RollbackTransaction => { - DbError::AlreadyExists("friend already exists, or too many friends".to_string()) + diesel::result::Error::RollbackTransaction => DbError::AlreadyExists( + "add_outgoing_sync_invitation, friend already exists, or too many friends".to_string(), + ), + _ => { + DbError::Unknown(format!("add_outgoing_sync_invitation, failed to insert friend: {}", e)) } - _ => DbError::Unknown(format!("failed to insert friend: {}", e)), }) } @@ -1810,48 +1899,90 @@ impl DB { write_key: Vec, max_friends: i32, ) -> Result { - return Err(DbError::Unimplemented( - "add_outgoing_async_invitation CHECK BELOW IMPL FOR NEW INERFACE".to_string(), - )); // TODO: check that we have space for async invitation - // let mut conn = self.connect().unwrap(); - // use crate::schema::address; - // use crate::schema::friend; - // use crate::schema::status; + let mut conn = self.connect().unwrap(); + use crate::schema::friend; + use crate::schema::outgoing_async_invitation; + use crate::schema::outgoing_chunk; - // // TODO: check if we already have outgoing async friend request, and if so, we don't support adding this right now - // // (we can currently only do one async friend request at a time) + // TODO: check if we already have outgoing async friend request, and if so, we don't support adding this right now + // (we can currently only do one async friend request at a time) - // let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { - // // IMPORTANT TODO: what if the friend already exists, but has been deleted? - // // We can either recycle the old entry, or create a new entry. - // // We choose the latter, which also erases the message history. - // // insert friend and address into database - // let friend = diesel::insert_into(friend::table) - // .values(friend_struct) - // .get_result::(conn_b)?; - // let uid = friend.uid; - // // we add the address and the status to their tables - // let address = ffi::Address { - // uid, - // read_index: address_struct.read_index, - // friend_request_message: address_struct.friend_request_message, - // friend_request_public_key: address_struct.friend_request_public_key, - // kx_public_key: address_struct.kx_public_key, - // ack_index: -1, // we don't allocate the ack index yet - // read_key: address_struct.read_key, - // write_key: address_struct.write_key, - // }; - // diesel::insert_into(address::table).values(&address).execute(conn_b)?; - // let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; - // diesel::insert_into(status::table).values(&status).execute(conn_b)?; - // Ok(()) - // }); - // match r { - // Ok(_) => Ok(()), - // Err(e) => Err(DbError::Unknown(format!("add_outgoing_async_friend_requests: {}", e))), - // } + let friend_fragment = ffi::FriendFragment { + unique_name: unique_name.to_string(), + display_name: display_name.to_string(), + invitation_progress: ffi::InvitationProgress::OutgoingSync, + deleted: false, + }; + let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + let has_space = self + .has_space_for_async_invitations() + .map_err(|e| diesel::result::Error::RollbackTransaction)?; + if !has_space { + return Err(diesel::result::Error::RollbackTransaction); + } + let can_add = self.can_add_friend(conn_b, unique_name, kx_public_key.clone(), max_friends)?; + if !can_add { + return Err(diesel::result::Error::RollbackTransaction); + } + let friend = diesel::insert_into(friend::table) + .values(&friend_fragment) + .returning(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + friend::deleted, + )) + .get_result::(conn_b)?; + + diesel::insert_into(outgoing_async_invitation::table) + .values(( + outgoing_async_invitation::friend_uid.eq(friend.uid), + outgoing_async_invitation::public_id.eq(public_id.to_string()), + outgoing_async_invitation::friend_request_public_key.eq(friend_request_public_key), + outgoing_async_invitation::kx_public_key.eq(kx_public_key.clone()), + outgoing_async_invitation::message.eq(message.to_string()), + outgoing_async_invitation::sent_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + + let my_public_id = self.get_public_id(conn_b)?; + + let new_seqnum = self.get_seqnum_for_new_chunk(conn_b, friend.uid)?; + + diesel::insert_into(outgoing_chunk::table) + .values(( + outgoing_chunk::to_friend.eq(friend.uid), + outgoing_chunk::sequence_number.eq(new_seqnum), + outgoing_chunk::chunks_start_sequence_number.eq(new_seqnum), + outgoing_chunk::message_uid.eq(-1), + outgoing_chunk::content.eq(my_public_id), // the content is public_id + outgoing_chunk::system.eq(true), + outgoing_chunk::system_message.eq(ffi::SystemMessage::OutgoingInvitation), + )) + .execute(conn_b)?; + + self.create_transmission_record( + conn_b, + friend.uid, + read_index, + read_key, + write_key, + max_friends, + )?; + + Ok(friend) + }); + r.map_err(|e| match e { + diesel::result::Error::RollbackTransaction => DbError::AlreadyExists( + "add_outgoing_async_invitation: friend already exists, or too many friends".to_string(), + ), + _ => { + DbError::Unknown(format!("add_outgoing_async_invitation, failed to insert friend: {}", e)) + } + }) } pub fn add_incoming_async_invitation( From 459fc12a3d54a7a51738030fa6e55b5d5f6c295b Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 23:02:40 -0700 Subject: [PATCH 09/22] outgoing async and sync now done --- daemon/db/db.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 43543f78..18dd75d3 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -1899,16 +1899,11 @@ impl DB { write_key: Vec, max_friends: i32, ) -> Result { - // TODO: check that we have space for async invitation - let mut conn = self.connect().unwrap(); use crate::schema::friend; use crate::schema::outgoing_async_invitation; use crate::schema::outgoing_chunk; - // TODO: check if we already have outgoing async friend request, and if so, we don't support adding this right now - // (we can currently only do one async friend request at a time) - let friend_fragment = ffi::FriendFragment { unique_name: unique_name.to_string(), display_name: display_name.to_string(), From 87f1fc29362e0527857b691ba8644dcf3b502064 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 23:10:44 -0700 Subject: [PATCH 10/22] it builds! --- daemon/db/db.rs | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 18dd75d3..be5a0a10 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -2034,23 +2034,29 @@ impl DB { pub fn get_outgoing_async_invitations( &self, ) -> Result, DbError> { - return Err(DbError::Unimplemented( - "get_outgoing_async_invitations CHECK BELOW IMPL FOR NEW INERFACE".to_string(), - )); - - // let mut conn = self.connect()?; // if error then crash function - // use crate::schema::friend; + let mut conn = self.connect()?; + use crate::schema::friend; + use crate::schema::outgoing_async_invitation; - // if let Ok(f) = friend::table - // .filter(friend::deleted.eq(false)) - // .filter(friend::request_progress.eq(ffi::InvitationProgress::OutgoingAsync)) - // .load::(&mut conn) - // { - // Ok(f) - // } else { - // Err(DbError::NotFound("failed to get friend".to_string())) - // } + friend::table + .filter(friend::deleted.eq(false)) + .filter(friend::invitation_progress.eq(ffi::InvitationProgress::OutgoingAsync)) + .inner_join(outgoing_async_invitation::table) + .select(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + outgoing_async_invitation::public_id, + outgoing_async_invitation::friend_request_public_key, + outgoing_async_invitation::kx_public_key, + outgoing_async_invitation::message, + outgoing_async_invitation::sent_at, + )) + .load::(&mut conn) + .map_err(|e| DbError::Unknown(format!("get_outgoing_async_invitations: {}", e))) } + pub fn get_incoming_invitations(&self) -> Result, DbError> { return Err(DbError::Unimplemented( "get_incoming_invitations CHECK BELOW IMPL FOR NEW INERFACE".to_string(), From 2242332c4b3497074a8f4753ae19a76ee73fe8aa Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 23:12:02 -0700 Subject: [PATCH 11/22] it builds! --- daemon/db/db.rs | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index be5a0a10..daf2dd4f 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -2027,10 +2027,26 @@ impl DB { } pub fn get_outgoing_sync_invitations(&self) -> Result, DbError> { - return Err(DbError::Unimplemented( - "get_outgoing_sync_invitations CHECK BELOW IMPL FOR NEW INERFACE".to_string(), - )); + let mut conn = self.connect()?; + use crate::schema::friend; + use crate::schema::outgoing_sync_invitation; + + friend::table + .filter(friend::deleted.eq(false)) + .filter(friend::invitation_progress.eq(ffi::InvitationProgress::OutgoingSync)) + .inner_join(outgoing_sync_invitation::table) + .select(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + outgoing_sync_invitation::story, + outgoing_sync_invitation::sent_at, + )) + .load::(&mut conn) + .map_err(|e| DbError::Unknown(format!("get_outgoing_sync_invitations: {}", e))) } + pub fn get_outgoing_async_invitations( &self, ) -> Result, DbError> { From 18899fda6cd0890c0a5e8aa1be1c7e6eae3a8187 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 23:13:50 -0700 Subject: [PATCH 12/22] it builds! --- daemon/db/db.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index daf2dd4f..99463271 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -2074,21 +2074,17 @@ impl DB { } pub fn get_incoming_invitations(&self) -> Result, DbError> { - return Err(DbError::Unimplemented( - "get_incoming_invitations CHECK BELOW IMPL FOR NEW INERFACE".to_string(), - )); - // let mut conn = self.connect()?; // if error then crash function - // use crate::schema::friend; + let mut conn = self.connect()?; // if error then crash function + use crate::schema::incoming_invitation; - // if let Ok(f) = friend::table - // .filter(friend::deleted.eq(false)) - // .filter(friend::request_progress.eq(ffi::InvitationProgress::Incoming)) - // .load::(&mut conn) - // { - // Ok(f) - // } else { - // Err(DbError::NotFound("failed to get friend".to_string())) - // } + incoming_invitation::table + .select(( + incoming_invitation::public_id, + incoming_invitation::message, + incoming_invitation::received_at, + )) + .load::(&mut conn) + .map_err(|e| DbError::Unknown(format!("get_incoming_invitations: {}", e))) } pub fn accept_incoming_invitation( From 7a91ccda156348fb406db074bd2b5a38d24c7a69 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Tue, 28 Jun 2022 23:18:21 -0700 Subject: [PATCH 13/22] it builds! --- daemon/db/db.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 99463271..9cccbd50 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -1899,7 +1899,7 @@ impl DB { write_key: Vec, max_friends: i32, ) -> Result { - let mut conn = self.connect().unwrap(); + let mut conn = self.connect()?; use crate::schema::friend; use crate::schema::outgoing_async_invitation; use crate::schema::outgoing_chunk; @@ -2151,23 +2151,21 @@ impl DB { // }) } - pub fn deny_incoming_invitation(&self, unique_name: &str) -> Result<(), DbError> { - return Err(DbError::Unimplemented( - "deny_incoming_invitation CHECK BELOW IMPL FOR NEW INTERFACE".to_string(), - )); - // // This function is called when the user rejects a friend request. - // let mut conn = self.connect().unwrap(); - // use crate::schema::friend; + pub fn deny_incoming_invitation(&self, public_id: &str) -> Result<(), DbError> { + // This function is called when the user rejects a friend request. + let mut conn = self.connect()?; + use crate::schema::incoming_invitation; - // // we change the deleted flag to true, meaning that the friend is deleted - // if let Ok(_) = diesel::update(friend::table.filter(friend::unique_name.eq(unique_name))) - // .set(friend::deleted.eq(true)) - // .execute(&mut conn) - // { - // Ok(()) - // } else { - // Err(DbError::Unknown("deny_async_friend_request FAILED".to_string())) - // } + // delete public_id from incoming_invitation + let r = diesel::delete( + incoming_invitation::table.filter(incoming_invitation::public_id.eq(public_id)), + ) + .execute(&mut conn); + + match r { + Ok(_) => Ok(()), + Err(e) => Err(DbError::Unknown(format!("deny_incoming_invitation: {}", e))), + } } // Inspiration code From 6bc1645f12b7a4d08dbf7cc8e555acc86cb3c349 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 00:42:01 -0700 Subject: [PATCH 14/22] it builds! --- daemon/db/db.rs | 269 ++++++++++++++++++++++++++++++++++-------------- 1 file changed, 192 insertions(+), 77 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 9cccbd50..7d190d41 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -282,6 +282,15 @@ pub mod ffi { pub sent_at: i64, // unix micros } #[derive(Queryable)] + struct JustOutgoingAsyncInvitation { + friend_uid: i32, + public_id: String, + friend_request_public_key: Vec, + kx_public_key: Vec, + message: String, + sent_at: i64, + } + #[derive(Queryable)] struct IncomingInvitation { pub public_id: String, pub message: String, @@ -558,6 +567,14 @@ pub mod ffi { ) -> Result<()>; // simply deletes the incoming invitation fn deny_incoming_invitation(&self, public_id: &str) -> Result<()>; + // receives an invitation system message, which means we should create a real friend! unless the public_id is incorrect + fn receive_invitation_system_message( + &self, + from_friend: i32, + sequence_number: i32, + public_id: &str, // we want this public_id to correspond to the one we already have + public_id_claimed_kx_public_key: Vec, // we want to verify that this is the same as we have on file! otherwise someone might be trying to deceive us + ) -> Result<()>; // // Messages @@ -572,14 +589,6 @@ pub mod ffi { ) -> Result; // receive the system message telling us to add the friend - fn receive_invitation_system_message( - &self, - from_friend: i32, - sequence_number: i32, - public_id: &str, // we want this public_id to correspond to the one we already have - public_id_claimed_kx_public_key: Vec, // we want to verify that this is the same as we have on file! otherwise someone might be trying to deceive us - ) -> Result<()>; - // fails if there is no chunk to send // prioritizes by the given uid in order from first to last try // if none of the priority people have a chunk to send, pick a random chunk @@ -1148,37 +1157,6 @@ impl DB { } } - pub fn receive_invitation_system_message( - &self, - from_friend: i32, - sequence_number: i32, - public_id: &str, - public_id_claimed_kx_public_key: Vec, - ) -> Result<(), DbError> { - let mut conn = self.connect()?; - use crate::schema::friend; - - let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { - let chunk_status = self.update_sequence_number(conn_b, from_friend, sequence_number)?; - if chunk_status == ffi::ReceiveChunkStatus::OldChunk { - return Ok(chunk_status); - } - // move the friend to become an actual friend - // TODO: the system message should contain their public key, and we should add it and verify it! - // TODO: we need to create the right kind of table record here!!! and delete the other table record - diesel::update(friend::table.find(from_friend)) - .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) - .execute(conn_b)?; - - Ok(ffi::ReceiveChunkStatus::NewChunk) - }); - - match r { - Ok(_) => Ok(()), - Err(e) => Err(DbError::Unknown(format!("receive_chunk: {}", e))), - } - } - pub fn chunk_to_send( &self, uid_priority: Vec, @@ -1980,50 +1958,156 @@ impl DB { }) } + fn complete_outgoing_async_friend( + &self, + conn: &mut SqliteConnection, + friend_uid: i32, + ) -> Result<(), diesel::result::Error> { + use crate::schema::complete_friend; + use crate::schema::friend; + use crate::schema::message; + use crate::schema::outgoing_async_invitation; + use crate::schema::sent; + // 1. make the friend complete + // 2. create a complete_friend and remove a async_outgoing_invitation + // 3. create a message for the original outgoing async message + conn.transaction(|conn_b| { + diesel::update(friend::table.find(friend_uid)) + .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) + .execute(conn_b)?; + + let async_invitation = outgoing_async_invitation::table + .find(friend_uid) + .select(( + outgoing_async_invitation::friend_uid, + outgoing_async_invitation::public_id, + outgoing_async_invitation::friend_request_public_key, + outgoing_async_invitation::kx_public_key, + outgoing_async_invitation::message, + outgoing_async_invitation::sent_at, + )) + .first::(conn_b)?; + + diesel::delete(outgoing_async_invitation::table.find(friend_uid)).execute(conn_b)?; + + diesel::insert_into(complete_friend::table) + .values(( + complete_friend::friend_uid.eq(friend_uid), + complete_friend::public_id.eq(async_invitation.public_id), + complete_friend::friend_request_public_key.eq(async_invitation.friend_request_public_key), + complete_friend::kx_public_key.eq(async_invitation.kx_public_key), + complete_friend::completed_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + + // finally, create a message + let message_uid = diesel::insert_into(message::table) + .values((message::content.eq(async_invitation.message),)) + .returning(message::uid) + .get_result::(conn_b)?; + + diesel::insert_into(sent::table) + .values(( + sent::uid.eq(message_uid), + sent::to_friend.eq(friend_uid), + sent::num_chunks.eq(1), + sent::sent_at.eq(async_invitation.sent_at), + sent::delivered.eq(true), + sent::delivered_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + + Ok(()) + }) + } + pub fn add_incoming_async_invitation( &self, public_id: &str, message: &str, ) -> Result<(), DbError> { - return Err(DbError::Unimplemented( - "add_incoming_async_invitation CHECK BELOW IMPL FOR NEW INERFACE".to_string(), - )); + // It is important to define the behavior of this function in the case of + // duplicate requests. i.e. when a friend (request) with the same public key + // is already in the database. Here's the definition for now. + // 2. If the friend is marked as accepted, then we ignore the request. + // 3. If the friend is marked as incoming, then we update the message. + // 4. If the friend is marked as outgoing async, then we approve this request. + // (if async outgoing, we just won't know, so we cannot take any special action) + // immediately. - // let mut conn = self.connect().unwrap(); - // use crate::schema::address; - // use crate::schema::friend; - // use crate::schema::status; - - // let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { - // // IMPORTANT TODO: what if the friend already exists? - // // We can either recycle the old entry, or create a new entry. - // // We choose the latter, which also erases the message history. - // // insert friend and address into database - // let friend = diesel::insert_into(friend::table) - // .values(friend_struct) - // .get_result::(conn_b)?; - // let uid = friend.uid; - // // we add the address and the status to their tables - // let address = ffi::Address { - // uid, - // read_index: address_struct.read_index, - // friend_request_message: address_struct.friend_request_message, - // friend_request_public_key: address_struct.friend_request_public_key, - // kx_public_key: address_struct.kx_public_key, - // ack_index: -1, // we don't allocate the ack index yet - // read_key: address_struct.read_key, - // write_key: address_struct.write_key, - // }; - // diesel::insert_into(address::table).values(&address).execute(conn_b)?; - // let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; - // diesel::insert_into(status::table).values(&status).execute(conn_b)?; - - // Ok(()) - // }); - // match r { - // Ok(_) => Ok(()), - // Err(e) => Err(DbError::Unknown(format!("add_incoming_async_friend_requests: {}", e))), - // } + let mut conn = self.connect()?; + use crate::schema::complete_friend; + use crate::schema::incoming_invitation; + use crate::schema::message; + use crate::schema::outgoing_async_invitation; + use crate::schema::received; + + let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + // check if the invitation already exists. if so, update the message, only. + let public_id_count = + incoming_invitation::table.find(public_id.to_string()).count().get_result::(conn_b)?; + if public_id_count > 0 { + diesel::update(incoming_invitation::table.find(public_id.to_string())) + .set(( + incoming_invitation::message.eq(message.to_string()), + incoming_invitation::received_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + return Ok(()); + } + + // if this is already an accepted friend, we ignore + let completed_friend_count = complete_friend::table + .filter(complete_friend::public_id.eq(public_id.to_string())) + .count() + .get_result::(conn_b)?; + if completed_friend_count > 0 { + return Ok(()); + } + + // if this is an outgoing async friend, we make it a real friend! + let outgoing_async_uids = outgoing_async_invitation::table + .filter(outgoing_async_invitation::public_id.eq(public_id.to_string())) + .select(outgoing_async_invitation::friend_uid) + .load::(conn_b)?; + if outgoing_async_uids.len() > 0 { + let friend_uid = outgoing_async_uids[0]; + self.complete_outgoing_async_friend(conn_b, friend_uid)?; + // add the incoming message as a real message! in the received table + let message_uid = diesel::insert_into(message::table) + .values((message::content.eq(message),)) + .returning(message::uid) + .get_result::(conn_b)?; + + diesel::insert_into(received::table) + .values(( + received::uid.eq(message_uid), + received::from_friend.eq(friend_uid), + received::num_chunks.eq(1), + received::received_at.eq(util::unix_micros_now()), + received::delivered.eq(true), + received::delivered_at.eq(util::unix_micros_now()), + received::seen.eq(false), + )) + .execute(conn_b)?; + return Ok(()); + } + + // the final case is that we just create a new incoming invitation + diesel::insert_into(incoming_invitation::table) + .values(( + incoming_invitation::public_id.eq(public_id.to_string()), + incoming_invitation::message.eq(message.to_string()), + incoming_invitation::received_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + + Ok(()) + }); + match r { + Ok(_) => Ok(()), + Err(e) => Err(DbError::Unknown(format!("add_incoming_async_friend_requests: {}", e))), + } } pub fn get_outgoing_sync_invitations(&self) -> Result, DbError> { @@ -2168,6 +2252,37 @@ impl DB { } } + pub fn receive_invitation_system_message( + &self, + from_friend: i32, + sequence_number: i32, + public_id: &str, + public_id_claimed_kx_public_key: Vec, + ) -> Result<(), DbError> { + let mut conn = self.connect()?; + use crate::schema::friend; + + let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + let chunk_status = self.update_sequence_number(conn_b, from_friend, sequence_number)?; + if chunk_status == ffi::ReceiveChunkStatus::OldChunk { + return Ok(chunk_status); + } + // move the friend to become an actual friend + // TODO: the system message should contain their public key, and we should add it and verify it! + // TODO: we need to create the right kind of table record here!!! and delete the other table record + diesel::update(friend::table.find(from_friend)) + .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) + .execute(conn_b)?; + + Ok(ffi::ReceiveChunkStatus::NewChunk) + }); + + match r { + Ok(_) => Ok(()), + Err(e) => Err(DbError::Unknown(format!("receive_invitation_system_message: {}", e))), + } + } + // Inspiration code // TODO: remove // pub fn create_friend( From 0e396f31fdccacf3dd14d84f31d64deefb9bdd98 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 01:05:44 -0700 Subject: [PATCH 15/22] it builds! --- daemon/db/db.rs | 93 ++++++++++++++++++++++++++++--- daemon/transmitter/transmitter.cc | 4 +- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 7d190d41..4a43f383 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -574,6 +574,7 @@ pub mod ffi { sequence_number: i32, public_id: &str, // we want this public_id to correspond to the one we already have public_id_claimed_kx_public_key: Vec, // we want to verify that this is the same as we have on file! otherwise someone might be trying to deceive us + public_id_claimed_friend_request_public_key: Vec, ) -> Result<()>; // @@ -1958,6 +1959,44 @@ impl DB { }) } + fn complete_outgoing_sync_friend( + &self, + conn: &mut SqliteConnection, + friend_uid: i32, + public_id: String, + friend_request_public_key: Vec, + ) -> Result<(), diesel::result::Error> { + // make the friend complete + // create a complete_friend and remove a sync_outgoing_invitation + use crate::schema::complete_friend; + use crate::schema::friend; + use crate::schema::outgoing_sync_invitation; + + conn.transaction(|conn_b| { + diesel::update(friend::table.find(friend_uid)) + .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) + .execute(conn_b)?; + + let kx_public_key = outgoing_sync_invitation::table + .find(friend_uid) + .select(outgoing_sync_invitation::kx_public_key) + .first::>(conn_b)?; + + diesel::delete(outgoing_sync_invitation::table.find(friend_uid)).execute(conn_b)?; + + diesel::insert_into(complete_friend::table) + .values(( + complete_friend::friend_uid.eq(friend_uid), + complete_friend::public_id.eq(public_id), + complete_friend::friend_request_public_key.eq(friend_request_public_key), + complete_friend::kx_public_key.eq(kx_public_key), + complete_friend::completed_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + + Ok(()) + }) + } fn complete_outgoing_async_friend( &self, conn: &mut SqliteConnection, @@ -2258,23 +2297,61 @@ impl DB { sequence_number: i32, public_id: &str, public_id_claimed_kx_public_key: Vec, + public_id_claimed_friend_request_public_key: Vec, ) -> Result<(), DbError> { let mut conn = self.connect()?; use crate::schema::friend; + use crate::schema::outgoing_async_invitation; + use crate::schema::outgoing_sync_invitation; let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { let chunk_status = self.update_sequence_number(conn_b, from_friend, sequence_number)?; if chunk_status == ffi::ReceiveChunkStatus::OldChunk { - return Ok(chunk_status); + return Ok(()); } - // move the friend to become an actual friend - // TODO: the system message should contain their public key, and we should add it and verify it! - // TODO: we need to create the right kind of table record here!!! and delete the other table record - diesel::update(friend::table.find(from_friend)) - .set(friend::invitation_progress.eq(ffi::InvitationProgress::Complete)) - .execute(conn_b)?; - Ok(ffi::ReceiveChunkStatus::NewChunk) + let friend_status = friend::table + .find(from_friend) + .select(friend::invitation_progress) + .get_result::(conn_b)?; + + match friend_status { + ffi::InvitationProgress::OutgoingSync => { + // verify that the kx_public_key is correct + let friend_kx_public_key = outgoing_sync_invitation::table + .find(from_friend) + .select(outgoing_sync_invitation::kx_public_key) + .get_result::>(conn_b)?; + if friend_kx_public_key != public_id_claimed_kx_public_key { + // something fishy is happening.... they are trying to deceive us! + return Err(diesel::result::Error::RollbackTransaction); + } + self.complete_outgoing_sync_friend( + conn_b, + from_friend, + public_id.to_string(), + public_id_claimed_friend_request_public_key, + )?; + } + ffi::InvitationProgress::OutgoingAsync => { + // verify that the public_id is correct + let friend_public_id = outgoing_async_invitation::table + .find(from_friend) + .select(outgoing_async_invitation::public_id) + .get_result::(conn_b)?; + if friend_public_id != public_id { + return Err(diesel::result::Error::RollbackTransaction); + } + // make it a real friend! + self.complete_outgoing_async_friend(conn_b, from_friend)?; + } + ffi::InvitationProgress::Complete => (), + _ => { + return Err(diesel::result::Error::RollbackTransaction); + } + } + + Ok(()) }); match r { diff --git a/daemon/transmitter/transmitter.cc b/daemon/transmitter/transmitter.cc index 488b0def..c8fcc725 100644 --- a/daemon/transmitter/transmitter.cc +++ b/daemon/transmitter/transmitter.cc @@ -324,7 +324,9 @@ auto Transmitter::retrieve() -> void { G.db->receive_invitation_system_message( f.uid, chunk.sequence_number(), chunk.system_message_data(), - string_to_rust_u8Vec(public_id.kx_public_key)); + string_to_rust_u8Vec(public_id.kx_public_key), + string_to_rust_u8Vec( + public_id.friend_request_public_key)); } catch (const rust::Error& e) { ASPHR_LOG_ERR("Unable to receive invitation system message", error_message, e.what(), friend_uid, f.uid); From b2679b127b8088a4c59479633bd442c6c0e2a409 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 01:43:38 -0700 Subject: [PATCH 16/22] it builds! --- daemon/db/db.rs | 246 ++++++++++++++++++------------------------------ 1 file changed, 91 insertions(+), 155 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 4a43f383..ae42df70 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -1880,6 +1880,7 @@ impl DB { ) -> Result { let mut conn = self.connect()?; use crate::schema::friend; + use crate::schema::incoming_invitation; use crate::schema::outgoing_async_invitation; use crate::schema::outgoing_chunk; @@ -1900,6 +1901,13 @@ impl DB { if !can_add { return Err(diesel::result::Error::RollbackTransaction); } + // if there is an incoming invitation here, we reject the new outgoing async invitation + // and tell the user to do just accept the invitation + let incoming_invitation_count = + incoming_invitation::table.find(public_id).count().get_result::(conn_b)?; + if incoming_invitation_count > 0 { + return Err(diesel::result::Error::RollbackTransaction); + } let friend = diesel::insert_into(friend::table) .values(&friend_fragment) .returning(( @@ -2222,56 +2230,90 @@ impl DB { write_key: Vec, max_friends: i32, ) -> Result<(), DbError> { - return Err(DbError::Unimplemented( - "accept_incoming_invitation CHECK BELOW IMPL FOR NEW INTERFACE".to_string(), - )); // // This function is called when the user accepts a friend request. - // let mut conn = self.connect().unwrap(); - - // use crate::schema::address; - // use crate::schema::friend; - - // // we change the progress field to ACTUAL_FRIEND, meaning that the friend is approved - // conn - // .transaction::<_, diesel::result::Error, _>(|conn_b| { - // // update the progress field of friend - // diesel::update(friend::table.filter(friend::unique_name.eq(unique_name))) - // .set(friend::request_progress.eq(ffi::InvitationProgress::Complete)) - // .execute(conn_b)?; - // // we need to get the uid of the friend to update the address - // let uid = friend::table - // .filter(friend::unique_name.eq(unique_name)) - // .select(friend::uid) - // .first::(conn_b)?; - // // another important thing is that we need to allocate ack_index for the address - // // we do this by updating the address table - // let ack_indices = address::table - // .inner_join(friend::table) - // .filter(friend::request_progress.eq(ffi::InvitationProgress::Complete)) - // .filter(friend::deleted.eq(false)) - // .select(address::ack_index) - // .load::(conn_b)?; - // let mut possible_ack_indices = Vec::::new(); - // for i in 0..max_friends { - // if !ack_indices.contains(&i) { - // possible_ack_indices.push(i); - // } - // } - // use rand::seq::SliceRandom; - // let ack_index_opt = possible_ack_indices.choose(&mut rand::thread_rng()); - // let ack_index = ack_index_opt.ok_or(diesel::result::Error::RollbackTransaction)?; - // diesel::update(address::table) - // .filter(address::uid.eq(uid)) - // .set(address::ack_index.eq(ack_index)) - // .execute(conn_b)?; - // Ok(()) - // }) - // .map_err(|e| match e { - // diesel::result::Error::RollbackTransaction => { - // DbError::AlreadyExists("no free ack index".to_string()) - // } - // _ => DbError::Unknown(format!("failed to insert address: {}", e)), - // }) + let mut conn = self.connect()?; + + use crate::schema::complete_friend; + use crate::schema::friend; + use crate::schema::incoming_invitation; + use crate::schema::message; + use crate::schema::received; + + let friend_fragment = ffi::FriendFragment { + unique_name: unique_name.to_string(), + display_name: display_name.to_string(), + invitation_progress: ffi::InvitationProgress::Complete, + deleted: false, + }; + // we change the progress field to Complete, meaning that the friend is approved + conn + .transaction::<_, diesel::result::Error, _>(|conn_b| { + let can_add = + self.can_add_friend(conn_b, unique_name, kx_public_key.clone(), max_friends)?; + if !can_add { + return Err(diesel::result::Error::RollbackTransaction); + } + // we create a new friend + let friend = diesel::insert_into(friend::table) + .values(&friend_fragment) + .returning(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + friend::deleted, + )) + .get_result::(conn_b)?; + + let inc_invitation = incoming_invitation::table + .filter(incoming_invitation::public_id.eq(public_id.to_string())) + .get_result::(conn_b)?; + + diesel::delete(incoming_invitation::table.find(public_id)).execute(conn_b)?; + + diesel::insert_into(complete_friend::table) + .values(( + complete_friend::friend_uid.eq(friend.uid), + complete_friend::public_id.eq(public_id), + complete_friend::friend_request_public_key.eq(friend_request_public_key), + complete_friend::kx_public_key.eq(kx_public_key), + complete_friend::completed_at.eq(util::unix_micros_now()), + )) + .execute(conn_b)?; + + // finally, create a message + let message_uid = diesel::insert_into(message::table) + .values((message::content.eq(inc_invitation.message),)) + .returning(message::uid) + .get_result::(conn_b)?; + + diesel::insert_into(received::table).values(( + received::uid.eq(message_uid), + received::from_friend.eq(friend.uid), + received::num_chunks.eq(1), + received::received_at.eq(inc_invitation.received_at), + received::delivered.eq(true), + received::delivered_at.eq(util::unix_micros_now()), + received::seen.eq(false), + )); + + self.create_transmission_record( + conn_b, + friend.uid, + read_index, + read_key, + write_key, + max_friends, + )?; + + Ok(()) + }) + .map_err(|e| match e { + diesel::result::Error::RollbackTransaction => { + DbError::AlreadyExists("no free ack index".to_string()) + } + _ => DbError::Unknown(format!("failed to insert address: {}", e)), + }) } pub fn deny_incoming_invitation(&self, public_id: &str) -> Result<(), DbError> { @@ -2359,110 +2401,4 @@ impl DB { Err(e) => Err(DbError::Unknown(format!("receive_invitation_system_message: {}", e))), } } - - // Inspiration code - // TODO: remove - // pub fn create_friend( - // &self, - // unique_name: &str, - // display_name: &str, - // public_id: &str, - // max_friends: i32, - // ) -> Result { - // let mut conn = self.connect()?; - // use crate::schema::friend; - - // let f = ffi::FriendFragment { - // unique_name: unique_name.to_string(), - // display_name: display_name.to_string(), - // public_id: public_id.to_string(), - // request_progress: ffi::InvitationProgress::Complete, - // deleted: false, - // }; - // let r = conn.transaction(|conn_b| { - // // check if a friend with this name already exists - // let count = friend::table - // .filter(friend::unique_name.eq(unique_name)) - // .count() - // .get_result::(conn_b)?; - // if count > 0 { - // return Err(diesel::result::Error::RollbackTransaction); - // } - // let count = friend::table.count().get_result::(conn_b)?; - // if count >= max_friends.into() { - // return Err(diesel::result::Error::RollbackTransaction); - // } - - // diesel::insert_into(friend::table).values(&f).get_result::(conn_b) - // }); - - // r.map_err(|e| match e { - // diesel::result::Error::RollbackTransaction => { - // DbError::AlreadyExists("friend already exists, or too many friends".to_string()) - // } - // _ => DbError::Unknown(format!("failed to insert friend: {}", e)), - // }) - // } - - // pub fn add_friend_address( - // &self, - // add_address: ffi::AddAddress, - // max_friends: i32, - // ) -> Result<(), DbError> { - // let mut conn = self.connect()?; - // use crate::schema::address; - // use crate::schema::friend; - // use crate::schema::status; - - // // transaction because we need to pick a new ack_index - // conn - // .transaction(|conn_b| { - // let uid = friend::table - // .filter(friend::unique_name.eq(add_address.unique_name)) - // .select(friend::uid) - // .first::(conn_b)?; - - // let ack_indices = address::table - // .inner_join(friend::table) - // .filter(friend::request_progress.eq(ffi::InvitationProgress::Complete)) - // .filter(friend::deleted.eq(false)) - // .select(address::ack_index) - // .load::(conn_b)?; - // let mut possible_ack_indices = Vec::::new(); - // for i in 0..max_friends { - // if !ack_indices.contains(&i) { - // possible_ack_indices.push(i); - // } - // } - // use rand::seq::SliceRandom; - // let ack_index_opt = possible_ack_indices.choose(&mut rand::thread_rng()); - // let ack_index = ack_index_opt.ok_or(diesel::result::Error::RollbackTransaction)?; - // let address = ffi::Address { - // uid, - // read_index: add_address.read_index, - // friend_request_message: add_address.friend_request_message, - // friend_request_public_key: add_address.friend_request_public_key, - // kx_public_key: add_address.kx_public_key, - // ack_index: *ack_index, - // read_key: add_address.read_key, - // write_key: add_address.write_key, - // }; - // diesel::insert_into(address::table).values(&address).execute(conn_b)?; - - // diesel::update(friend::table.find(uid)) - // .set(friend::request_progress.eq(ffi::InvitationProgress::Complete)) - // .execute(conn_b)?; - - // let status = ffi::Status { uid, sent_acked_seqnum: 0, received_seqnum: 0 }; - // diesel::insert_into(status::table).values(&status).execute(conn_b)?; - - // Ok(()) - // }) - // .map_err(|e| match e { - // diesel::result::Error::RollbackTransaction => { - // DbError::AlreadyExists("no free ack index".to_string()) - // } - // _ => DbError::Unknown(format!("failed to insert address: {}", e)), - // }) - // } } From f9c2adab537b281bca2b3163fdfd56c259361102 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 01:44:46 -0700 Subject: [PATCH 17/22] fix warnings --- daemon/db/db.rs | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index ae42df70..a47214a8 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -1893,7 +1893,7 @@ impl DB { let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { let has_space = self .has_space_for_async_invitations() - .map_err(|e| diesel::result::Error::RollbackTransaction)?; + .map_err(|_| diesel::result::Error::RollbackTransaction)?; if !has_space { return Err(diesel::result::Error::RollbackTransaction); } @@ -2287,15 +2287,17 @@ impl DB { .returning(message::uid) .get_result::(conn_b)?; - diesel::insert_into(received::table).values(( - received::uid.eq(message_uid), - received::from_friend.eq(friend.uid), - received::num_chunks.eq(1), - received::received_at.eq(inc_invitation.received_at), - received::delivered.eq(true), - received::delivered_at.eq(util::unix_micros_now()), - received::seen.eq(false), - )); + diesel::insert_into(received::table) + .values(( + received::uid.eq(message_uid), + received::from_friend.eq(friend.uid), + received::num_chunks.eq(1), + received::received_at.eq(inc_invitation.received_at), + received::delivered.eq(true), + received::delivered_at.eq(util::unix_micros_now()), + received::seen.eq(false), + )) + .execute(conn_b)?; self.create_transmission_record( conn_b, From e1a9e1140080b15190f5c3c5b1a1098fc621ea15 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 02:06:12 -0700 Subject: [PATCH 18/22] add check_rep --- daemon/db/db.rs | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index a47214a8..84288ebb 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -149,6 +149,19 @@ impl From for i32 { } } +impl From for DbError { + fn from(e: diesel::result::Error) -> Self { + match e { + diesel::result::Error::NotFound => DbError::NotFound("not found".into()), + diesel::result::Error::DatabaseError( + diesel::result::DatabaseErrorKind::UniqueViolation, + _, + ) => DbError::AlreadyExists("already exists".into()), + _ => DbError::Internal(format!("{:?}", e)), + } + } +} + impl Queryable, DB> for I32ButMinusOneIsNone where @@ -623,6 +636,8 @@ pub fn init(address: &str) -> Result, DbError> { use diesel_migrations::MigrationHarness; conn.run_pending_migrations(MIGRATIONS).map_err(|e| DbError::Unavailable(e.to_string()))?; + db.check_rep(&mut conn)?; + Ok(Box::new(db)) } @@ -645,6 +660,84 @@ impl DB { } } + #[cfg(debug_assertions)] + pub fn check_rep(&self, conn: &mut SqliteConnection) -> Result<(), DbError> { + use crate::schema::*; + conn.transaction::<(), DbError, _>(|conn_b| { + // invitation_progress should correspond to the correct table existing + let friends = friend::table + .filter(friend::deleted.eq(false)) + .select(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + friend::deleted, + )) + .load::(conn_b) + .map_err(|e| DbError::Unknown(format!("failed to load friends, {}", e,)))?; + + for friend in friends { + match friend.invitation_progress { + ffi::InvitationProgress::Complete => { + let complete_count = complete_friend::table + .filter(complete_friend::friend_uid.eq(friend.uid)) + .count() + .get_result::(conn_b) + .map_err(|e| DbError::Internal(format!("unable to get complete_count: {}", e,)))?; + if complete_count != 1 { + return Err(DbError::Internal(format!( + "complete_friend table has {} entries for friend with uid {}", + complete_count, friend.uid, + ))); + } + } + ffi::InvitationProgress::OutgoingSync => { + let sync_count = outgoing_sync_invitation::table + .filter(outgoing_sync_invitation::friend_uid.eq(friend.uid)) + .count() + .get_result::(conn_b) + .map_err(|e| DbError::Internal(format!("unable to get sync_count: {}", e,)))?; + if sync_count != 1 { + return Err(DbError::Internal(format!( + "outgoing_sync_invitation table has {} entries for friend with uid {}", + sync_count, friend.uid, + ))); + } + } + ffi::InvitationProgress::OutgoingAsync => { + let async_count = outgoing_async_invitation::table + .filter(outgoing_async_invitation::friend_uid.eq(friend.uid)) + .count() + .get_result::(conn_b) + .map_err(|e| DbError::Internal(format!("unable to get async_count: {}", e,)))?; + if async_count != 1 { + return Err(DbError::Internal(format!( + "outgoing_async_invitation table has {} entries for friend with uid {}", + async_count, friend.uid, + ))); + } + } + _ => { + return Err(DbError::Internal(format!( + "friend with uid {} has unsupported invitation_progress", + friend.uid, + ))) + } + } + } + + Ok(()) + })?; + + Ok(()) + } + + #[cfg(not(debug_assertions))] + pub fn check_rep(&self, conn: &mut SqliteConnection) -> Result<(), DbError> { + Ok(()) + } + /// # Safety /// /// This dumps the database using pure sql which uses pointers written by hand. Possibly memory leaks. From ec304191b8689a0b0ab24602f3bbfc9cbd5e4c92 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 09:26:07 -0700 Subject: [PATCH 19/22] some changes with errors --- daemon/README.md | 10 +++- daemon/db/db.rs | 148 ++++++++++++++++++++++++----------------------- 2 files changed, 85 insertions(+), 73 deletions(-) diff --git a/daemon/README.md b/daemon/README.md index 86e710e5..d1e038e8 100644 --- a/daemon/README.md +++ b/daemon/README.md @@ -6,7 +6,7 @@ Set up VSCode rust-analyzer (very recommended!): bazelisk run @rules_rust//tools/rust_analyzer:gen_rust_project ``` -# Database changes +## Database changes We want to create a migration! @@ -25,3 +25,11 @@ and ``` ./diesel-cli.sh migration redo ``` + +## Debugging + +For debugging the Rust code, run + +``` +bazelisk test //... --test_env=RUST_BACKTRACE=1 +``` diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 84288ebb..a830fbd8 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -152,12 +152,12 @@ impl From for i32 { impl From for DbError { fn from(e: diesel::result::Error) -> Self { match e { - diesel::result::Error::NotFound => DbError::NotFound("not found".into()), + diesel::result::Error::NotFound => DbError::NotFound(format!("{}", e)), diesel::result::Error::DatabaseError( diesel::result::DatabaseErrorKind::UniqueViolation, _, - ) => DbError::AlreadyExists("already exists".into()), - _ => DbError::Internal(format!("{:?}", e)), + ) => DbError::AlreadyExists(format!("{}", e)), + _ => DbError::Internal(format!("{:?}: {}", e, e)), } } } @@ -663,72 +663,72 @@ impl DB { #[cfg(debug_assertions)] pub fn check_rep(&self, conn: &mut SqliteConnection) -> Result<(), DbError> { use crate::schema::*; - conn.transaction::<(), DbError, _>(|conn_b| { - // invitation_progress should correspond to the correct table existing - let friends = friend::table - .filter(friend::deleted.eq(false)) - .select(( - friend::uid, - friend::unique_name, - friend::display_name, - friend::invitation_progress, - friend::deleted, - )) - .load::(conn_b) - .map_err(|e| DbError::Unknown(format!("failed to load friends, {}", e,)))?; - - for friend in friends { - match friend.invitation_progress { - ffi::InvitationProgress::Complete => { - let complete_count = complete_friend::table - .filter(complete_friend::friend_uid.eq(friend.uid)) - .count() - .get_result::(conn_b) - .map_err(|e| DbError::Internal(format!("unable to get complete_count: {}", e,)))?; - if complete_count != 1 { - return Err(DbError::Internal(format!( - "complete_friend table has {} entries for friend with uid {}", - complete_count, friend.uid, - ))); + // we unwrap everything here because we're in check_rep! we want to fail fast. + conn + .transaction::<(), DbError, _>(|conn_b| { + // invitation_progress should correspond to the correct table existing + let friends = friend::table + .filter(friend::deleted.eq(false)) + .select(( + friend::uid, + friend::unique_name, + friend::display_name, + friend::invitation_progress, + friend::deleted, + )) + .load::(conn_b) + .unwrap(); + + for friend in friends { + match friend.invitation_progress { + ffi::InvitationProgress::Complete => { + let complete_count = complete_friend::table + .filter(complete_friend::friend_uid.eq(friend.uid)) + .count() + .get_result::(conn_b) + .unwrap(); + if complete_count != 1 { + panic!( + "complete_friend table has {} entries for friend with uid {}", + complete_count, friend.uid, + ); + } } - } - ffi::InvitationProgress::OutgoingSync => { - let sync_count = outgoing_sync_invitation::table - .filter(outgoing_sync_invitation::friend_uid.eq(friend.uid)) - .count() - .get_result::(conn_b) - .map_err(|e| DbError::Internal(format!("unable to get sync_count: {}", e,)))?; - if sync_count != 1 { - return Err(DbError::Internal(format!( - "outgoing_sync_invitation table has {} entries for friend with uid {}", - sync_count, friend.uid, - ))); + ffi::InvitationProgress::OutgoingSync => { + let sync_count = outgoing_sync_invitation::table + .filter(outgoing_sync_invitation::friend_uid.eq(friend.uid)) + .count() + .get_result::(conn_b) + .unwrap(); + if sync_count != 1 { + panic!( + "outgoing_sync_invitation table has {} entries for friend with uid {}", + sync_count, friend.uid, + ); + } } - } - ffi::InvitationProgress::OutgoingAsync => { - let async_count = outgoing_async_invitation::table - .filter(outgoing_async_invitation::friend_uid.eq(friend.uid)) - .count() - .get_result::(conn_b) - .map_err(|e| DbError::Internal(format!("unable to get async_count: {}", e,)))?; - if async_count != 1 { - return Err(DbError::Internal(format!( - "outgoing_async_invitation table has {} entries for friend with uid {}", - async_count, friend.uid, - ))); + ffi::InvitationProgress::OutgoingAsync => { + let async_count = outgoing_async_invitation::table + .filter(outgoing_async_invitation::friend_uid.eq(friend.uid)) + .count() + .get_result::(conn_b) + .unwrap(); + if async_count != 1 { + panic!( + "outgoing_async_invitation table has {} entries for friend with uid {}", + async_count, friend.uid, + ); + } + } + _ => { + panic!("friend with uid {} has unsupported invitation_progress", friend.uid,) } - } - _ => { - return Err(DbError::Internal(format!( - "friend with uid {} has unsupported invitation_progress", - friend.uid, - ))) } } - } - Ok(()) - })?; + Ok(()) + }) + .unwrap(); Ok(()) } @@ -1983,23 +1983,28 @@ impl DB { invitation_progress: ffi::InvitationProgress::OutgoingSync, deleted: false, }; - let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + conn.transaction::<_, DbError, _>(|conn_b| { let has_space = self - .has_space_for_async_invitations() - .map_err(|_| diesel::result::Error::RollbackTransaction)?; + .has_space_for_async_invitations()?; if !has_space { - return Err(diesel::result::Error::RollbackTransaction); + return Err(DbError::ResourceExhausted(format!( + "add_outgoing_async_invitation, too many async invitations (currently max is 1)" + ))); } let can_add = self.can_add_friend(conn_b, unique_name, kx_public_key.clone(), max_friends)?; if !can_add { - return Err(diesel::result::Error::RollbackTransaction); + return Err(DbError::ResourceExhausted(format!( + "add_outgoing_async_invitation, cannot add friend for whatever reason" + ))); } // if there is an incoming invitation here, we reject the new outgoing async invitation // and tell the user to do just accept the invitation let incoming_invitation_count = incoming_invitation::table.find(public_id).count().get_result::(conn_b)?; if incoming_invitation_count > 0 { - return Err(diesel::result::Error::RollbackTransaction); + return Err(DbError::ResourceExhausted(format!( + "add_outgoing_async_invitation, there is an incoming invitation for this public_id. please accept it" + ))); } let friend = diesel::insert_into(friend::table) .values(&friend_fragment) @@ -2049,10 +2054,9 @@ impl DB { )?; Ok(friend) - }); - r.map_err(|e| match e { + }).map_err(|e| match e { diesel::result::Error::RollbackTransaction => DbError::AlreadyExists( - "add_outgoing_async_invitation: friend already exists, or too many friends".to_string(), + "add_outgoing_async_invitation, friend already exists, or too many friends".to_string(), ), _ => { DbError::Unknown(format!("add_outgoing_async_invitation, failed to insert friend: {}", e)) From 78dc413ec06dec51b284e4ddf57f632bf0c8b966 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 09:28:42 -0700 Subject: [PATCH 20/22] make build --- daemon/db/db.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/daemon/db/db.rs b/daemon/db/db.rs index a830fbd8..4bf1e091 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -2054,13 +2054,6 @@ impl DB { )?; Ok(friend) - }).map_err(|e| match e { - diesel::result::Error::RollbackTransaction => DbError::AlreadyExists( - "add_outgoing_async_invitation, friend already exists, or too many friends".to_string(), - ), - _ => { - DbError::Unknown(format!("add_outgoing_async_invitation, failed to insert friend: {}", e)) - } }) } From 3811179bb728340bd6c3335abbc2eb8e5091e1e5 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 10:39:18 -0700 Subject: [PATCH 21/22] initial tests passing!!! --- daemon/BUILD | 1 + daemon/db/db.rs | 179 +++++++++++++++++++++++++++++---------------- daemon/db/lib.rs | 2 + daemon/db/tests.rs | 38 ++++++++-- 4 files changed, 151 insertions(+), 69 deletions(-) diff --git a/daemon/BUILD b/daemon/BUILD index 1d6912be..79cc57a6 100644 --- a/daemon/BUILD +++ b/daemon/BUILD @@ -47,6 +47,7 @@ rust_static_library( compile_data = [":migrations"], deps = [ ":db_bridge", + "@crate_index//:anyhow", "@crate_index//:diesel", "@crate_index//:diesel_migrations", "@crate_index//:libsqlite3-sys", diff --git a/daemon/db/db.rs b/daemon/db/db.rs index 4bf1e091..0539ef16 100644 --- a/daemon/db/db.rs +++ b/daemon/db/db.rs @@ -2,6 +2,8 @@ use diesel::prelude::*; use std::{error::Error, fmt}; +use anyhow::Context; + #[derive(Debug)] #[allow(dead_code)] pub enum DbError { @@ -24,6 +26,20 @@ pub enum DbError { Unauthenticated(String), } +#[derive(Queryable)] +struct OutgoingChunkPlusPlusMinusNumChunks { + pub to_friend: i32, + pub sequence_number: i32, + pub chunks_start_sequence_number: i32, + #[diesel(deserialize_as = crate::db::I32ButMinusOneIsNone)] + pub message_uid: i32, // -1 iff system message + pub content: String, + pub write_key: Vec, + pub system: bool, + #[diesel(deserialize_as = ffi::SystemMessage)] + pub system_message: ffi::SystemMessage, +} + impl Error for DbError {} impl fmt::Display for DbError { @@ -235,7 +251,7 @@ pub mod ffi { OutgoingSync, Complete, } - #[derive(Queryable)] + #[derive(Queryable, Debug)] struct Friend { pub uid: i32, pub unique_name: String, @@ -1254,7 +1270,7 @@ impl DB { pub fn chunk_to_send( &self, uid_priority: Vec, - ) -> Result { + ) -> Result { let mut conn = self.connect()?; use crate::schema::friend; @@ -1265,7 +1281,7 @@ impl DB { // We could do probably this in one query, by joining on the select statement // and then joining. Diesel doesn't typecheck this, and maybe it is unsafe, so // let's just do a transaction. - let r = conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + conn.transaction::<_, anyhow::Error, _>(|conn_b| { let q = outgoing_chunk::table .group_by(outgoing_chunk::to_friend) .select((outgoing_chunk::to_friend, diesel::dsl::min(outgoing_chunk::sequence_number))); @@ -1279,7 +1295,7 @@ impl DB { acc }); if first_chunk_per_friend.is_empty() { - return Err(diesel::result::Error::NotFound); + return Err(diesel::result::Error::NotFound).map_err(|e| e.into()); } let chosen_chunk: (i32, i32) = (|| { for uid in uid_priority { @@ -1291,29 +1307,63 @@ impl DB { let index = rng % first_chunk_per_friend.len(); first_chunk_per_friend.remove(index) })(); - let chunk_plusplus = outgoing_chunk::table + // special case: control message + let is_system_message = outgoing_chunk::table .find(chosen_chunk) - .inner_join(friend::table.inner_join(transmission::table)) - .inner_join(sent::table) - .select(( - outgoing_chunk::to_friend, - outgoing_chunk::sequence_number, - outgoing_chunk::chunks_start_sequence_number, - outgoing_chunk::message_uid, - outgoing_chunk::content, - transmission::write_key, - sent::num_chunks, - outgoing_chunk::system, - outgoing_chunk::system_message, - )) - .first::(conn_b)?; - Ok(chunk_plusplus) - }); - - match r { - Ok(chunk_plusplus) => Ok(chunk_plusplus), - Err(e) => Err(DbError::NotFound(format!("chunk_to_send: {}", e))), - } + .select(outgoing_chunk::system) + .first::(conn_b)?; + if is_system_message { + // the number of chunks for system messages is always 1 + // unfortunately, system messages do not have an entry in the sent table + // so the code for non-system messages do not work. + let chunk_plusplus_minusnumchunks = outgoing_chunk::table + .find(chosen_chunk) + .inner_join(friend::table.inner_join(transmission::table)) + .select(( + outgoing_chunk::to_friend, + outgoing_chunk::sequence_number, + outgoing_chunk::chunks_start_sequence_number, + outgoing_chunk::message_uid, + outgoing_chunk::content, + transmission::write_key, + outgoing_chunk::system, + outgoing_chunk::system_message, + )) + .first::(conn_b) + .context("chunk_to_send, cannot find chosen chunk in the outgoing_chunk table")?; + let chunk_plusplus = ffi::OutgoingChunkPlusPlus { + to_friend: chunk_plusplus_minusnumchunks.to_friend, + sequence_number: chunk_plusplus_minusnumchunks.sequence_number, + chunks_start_sequence_number: chunk_plusplus_minusnumchunks.chunks_start_sequence_number, + message_uid: chunk_plusplus_minusnumchunks.message_uid, + content: chunk_plusplus_minusnumchunks.content, + write_key: chunk_plusplus_minusnumchunks.write_key, + num_chunks: 1, + system: chunk_plusplus_minusnumchunks.system, + system_message: chunk_plusplus_minusnumchunks.system_message, + }; + Ok(chunk_plusplus) + } else { + let chunk_plusplus = outgoing_chunk::table + .find(chosen_chunk) + .inner_join(friend::table.inner_join(transmission::table)) + .inner_join(sent::table) + .select(( + outgoing_chunk::to_friend, + outgoing_chunk::sequence_number, + outgoing_chunk::chunks_start_sequence_number, + outgoing_chunk::message_uid, + outgoing_chunk::content, + transmission::write_key, + sent::num_chunks, + outgoing_chunk::system, + outgoing_chunk::system_message, + )) + .first::(conn_b) + .context("chunk_to_send, cannot find chosen chunk in the outgoing_chunk table")?; + Ok(chunk_plusplus) + } + }) } pub fn acks_to_send(&self) -> Result, DbError> { @@ -1408,14 +1458,18 @@ impl DB { q.first(conn) } + // input: the uid of a friend. + // output: the next sequence number to use for sending a message to that friend. + // this is equal to the previous seqnum + 1, + // or 1 if no previous message exist fn get_seqnum_for_new_chunk( &self, conn: &mut SqliteConnection, friend_uid: i32, - ) -> Result { + ) -> Result { use crate::schema::outgoing_chunk; use crate::schema::transmission; - conn.transaction::<_, diesel::result::Error, _>(|conn_b| { + conn.transaction::<_, anyhow::Error, _>(|conn_b| { // get the correct sequence number. This is the last sequence number + 1. // this is either the maximum sequence number in the table + 1, // or the sent_acked_seqnum + 1 if there are no outgoing chunks. @@ -1424,12 +1478,13 @@ impl DB { .select(outgoing_chunk::sequence_number) .order_by(outgoing_chunk::sequence_number.desc()) .limit(1) - .load::(conn_b)?; + .load::(conn_b) + .context("get_seqnum_for_new_chunk, failed to find old sequence number from the outgoing chunk table")?; let old_seqnum = match maybe_old_seqnum.len() { 0 => transmission::table .find(friend_uid) .select(transmission::sent_acked_seqnum) - .first::(conn_b)?, + .first::(conn_b).context("get_seqnum_for_new_chunk, failed to find seqnum from the transmission table")?, _ => maybe_old_seqnum[0], }; let new_seqnum = old_seqnum + 1; @@ -1454,7 +1509,7 @@ impl DB { use crate::schema::sent; conn - .transaction::<_, diesel::result::Error, _>(|conn_b| { + .transaction::<_, anyhow::Error, _>(|conn_b| { let friend_uid = self.get_friend_uid_by_unique_name(conn_b, to_unique_name)?; let message_uid = diesel::insert_into(message::table) @@ -1894,10 +1949,13 @@ impl DB { invitation_progress: ffi::InvitationProgress::OutgoingSync, deleted: false, }; - let r = conn.transaction(|conn_b| { + let r = conn.transaction::<_, anyhow::Error, _>(|conn_b| { let can_add = self.can_add_friend(conn_b, unique_name, kx_public_key.clone(), max_friends)?; if !can_add { - return Err(diesel::result::Error::RollbackTransaction); + return Err(DbError::InvalidArgument( + "add_outgoing_sync_invitation, cannot add friend for whatever reason".to_string(), + )) + .map_err(|e| e.into()); } let friend = diesel::insert_into(friend::table) @@ -1948,13 +2006,8 @@ impl DB { Ok(friend) }); - r.map_err(|e| match e { - diesel::result::Error::RollbackTransaction => DbError::AlreadyExists( - "add_outgoing_sync_invitation, friend already exists, or too many friends".to_string(), - ), - _ => { - DbError::Unknown(format!("add_outgoing_sync_invitation, failed to insert friend: {}", e)) - } + r.map_err(|e| { + DbError::Unknown(format!("add_outgoing_sync_invitation, failed to insert friend: {}", e)) }) } @@ -1970,7 +2023,7 @@ impl DB { read_key: Vec, write_key: Vec, max_friends: i32, - ) -> Result { + ) -> Result { let mut conn = self.connect()?; use crate::schema::friend; use crate::schema::incoming_invitation; @@ -1983,19 +2036,19 @@ impl DB { invitation_progress: ffi::InvitationProgress::OutgoingSync, deleted: false, }; - conn.transaction::<_, DbError, _>(|conn_b| { + conn.transaction::<_, anyhow::Error, _>(|conn_b| { let has_space = self .has_space_for_async_invitations()?; if !has_space { return Err(DbError::ResourceExhausted(format!( - "add_outgoing_async_invitation, too many async invitations (currently max is 1)" - ))); + "add_outgoing_async_invitation, too many async invitations at the same time(currently max is 1)" + ))).map_err(|e| e.into()); } let can_add = self.can_add_friend(conn_b, unique_name, kx_public_key.clone(), max_friends)?; if !can_add { return Err(DbError::ResourceExhausted(format!( - "add_outgoing_async_invitation, cannot add friend for whatever reason" - ))); + "add_outgoing_async_invitation, cannot add friend because limit number of friend reached." + ))).map_err(|e| e.into()); } // if there is an incoming invitation here, we reject the new outgoing async invitation // and tell the user to do just accept the invitation @@ -2004,7 +2057,7 @@ impl DB { if incoming_invitation_count > 0 { return Err(DbError::ResourceExhausted(format!( "add_outgoing_async_invitation, there is an incoming invitation for this public_id. please accept it" - ))); + ))).map_err(|e| e.into()); } let friend = diesel::insert_into(friend::table) .values(&friend_fragment) @@ -2015,7 +2068,7 @@ impl DB { friend::invitation_progress, friend::deleted, )) - .get_result::(conn_b)?; + .get_result::(conn_b).context("add_outgoing_async_invitation, failed to insert friend into friend::table")?; diesel::insert_into(outgoing_async_invitation::table) .values(( @@ -2026,32 +2079,32 @@ impl DB { outgoing_async_invitation::message.eq(message.to_string()), outgoing_async_invitation::sent_at.eq(util::unix_micros_now()), )) - .execute(conn_b)?; + .execute(conn_b).context("add_outgoing_async_invitation, failed to insert friend into outgoing_async_invitation::table")?; - let my_public_id = self.get_public_id(conn_b)?; + self.create_transmission_record( + conn_b, + friend.uid, + read_index, + read_key, + write_key, + max_friends, + )?; - let new_seqnum = self.get_seqnum_for_new_chunk(conn_b, friend.uid)?; + let my_public_id = self.get_public_id(conn_b).context("add_outgoing_async_invitation, failed to get public_id")?; + + let new_seqnum = self.get_seqnum_for_new_chunk(conn_b, friend.uid).context("add_outgoing_async_invitation, failed to get new_seqnum")?; diesel::insert_into(outgoing_chunk::table) .values(( outgoing_chunk::to_friend.eq(friend.uid), outgoing_chunk::sequence_number.eq(new_seqnum), outgoing_chunk::chunks_start_sequence_number.eq(new_seqnum), - outgoing_chunk::message_uid.eq(-1), + outgoing_chunk::message_uid.eq::>(None), // message UID should be null outgoing_chunk::content.eq(my_public_id), // the content is public_id outgoing_chunk::system.eq(true), outgoing_chunk::system_message.eq(ffi::SystemMessage::OutgoingInvitation), )) - .execute(conn_b)?; - - self.create_transmission_record( - conn_b, - friend.uid, - read_index, - read_key, - write_key, - max_friends, - )?; + .execute(conn_b).context("add_outgoing_async_invitation, failed to insert friend into outgoing_chunk::table")?; Ok(friend) }) @@ -2064,7 +2117,7 @@ impl DB { public_id: String, friend_request_public_key: Vec, ) -> Result<(), diesel::result::Error> { - // make the friend complete + // make the friend > // create a complete_friend and remove a sync_outgoing_invitation use crate::schema::complete_friend; use crate::schema::friend; diff --git a/daemon/db/lib.rs b/daemon/db/lib.rs index 1083e117..fa914c9c 100644 --- a/daemon/db/lib.rs +++ b/daemon/db/lib.rs @@ -3,6 +3,8 @@ extern crate diesel; extern crate libsqlite3_sys; +extern crate anyhow; + pub mod db; pub mod schema; diff --git a/daemon/db/tests.rs b/daemon/db/tests.rs index a7531f98..8cb31ac2 100644 --- a/daemon/db/tests.rs +++ b/daemon/db/tests.rs @@ -14,7 +14,7 @@ fn get_registration_fragment() -> ffi::RegistrationFragment { let pir_secret_key: Vec = br#""hi hi"#.to_vec(); let pir_galois_key: Vec = br#""hi hi hi"#.to_vec(); let authentication_token: String = "X6H3ILWIrDGThjbi4IpYfWGtJ3YWdMIf".to_string(); - let public_id: String = "wwww".to_string(); + let public_id: String = "my_public_id".to_string(); ffi::RegistrationFragment { friend_request_public_key, @@ -127,7 +127,8 @@ fn test_receive_msg() { ) .unwrap(); // will be auto accepted! - db.add_incoming_async_invitation("hi_this_is_a_public_id", "hi from freidn 1").unwrap(); + db.add_incoming_async_invitation("hi_this_is_a_public_id", "invitation: hi from friend 1") + .unwrap(); let msg = "hi im a chunk"; let chunk_status = db @@ -154,13 +155,15 @@ fn test_receive_msg() { }) .unwrap(); - assert_eq!(msgs.len(), 1); - assert_eq!(msgs[0].content, msg); + // 1 invitation message + 1 actual message + assert_eq!(msgs.len(), 2); + assert_eq!(msgs[0].content, "invitation: hi from friend 1"); + assert_eq!(msgs[1].content, msg); let mut conn = SqliteConnection::establish(&db.address).unwrap(); use crate::schema::transmission; let status_pair = transmission::table - .find(msgs[0].uid) + .find(f.uid) .select((transmission::sent_acked_seqnum, transmission::received_seqnum)) .first::<(i32, i32)>(&mut conn) .unwrap(); @@ -197,19 +200,42 @@ fn test_send_msg() { 20, ) .unwrap(); + + println!("f: {:?}", f); + // will be auto accepted! db.add_incoming_async_invitation("hi_this_is_a_public_id", "hi from freidn 1").unwrap(); let msg = "hi im a single chunk"; db.queue_message_to_send("friend_1", msg, vec![msg.to_string()]).unwrap(); + unsafe { + db.dump(); + } + let chunk_to_send = db.chunk_to_send(vec![]).unwrap(); + // the chunk to send will be the system message for the outgoing invitation + // the content is the public id assert!(chunk_to_send.to_friend == f.uid); assert!(chunk_to_send.sequence_number == 1); assert!(chunk_to_send.chunks_start_sequence_number == 1); // assert!(chunk_to_send.message_uid == 0); // we don't necessarily know what message_uid sqlite chooses - assert!(chunk_to_send.content == msg); + assert!(chunk_to_send.content == "my_public_id"); + assert!(chunk_to_send.write_key == br#"wwww"#.to_vec()); + assert!(chunk_to_send.num_chunks == 1); + + db.receive_ack(f.uid, 1).unwrap(); + + let chunk_to_send = db.chunk_to_send(vec![]).unwrap(); + + // the chunk to send will be the system message for the outgoing invitation + // the content is the public id + assert!(chunk_to_send.to_friend == f.uid); + assert!(chunk_to_send.sequence_number == 2); + assert!(chunk_to_send.chunks_start_sequence_number == 2); + // assert!(chunk_to_send.message_uid == 0); // we don't necessarily know what message_uid sqlite chooses + assert!(chunk_to_send.content == "hi im a single chunk"); assert!(chunk_to_send.write_key == br#"wwww"#.to_vec()); assert!(chunk_to_send.num_chunks == 1); } From cff4555ae95e1bebf1bb696a537d8634da2618f5 Mon Sep 17 00:00:00 2001 From: Arvid Lunnemark Date: Wed, 29 Jun 2022 17:39:39 +0000 Subject: [PATCH 22/22] =?UTF-8?q?=F0=9F=94=84=20"initial=20tests=20passing?= =?UTF-8?q?!!!=20"=20Update=20anysphere/asphr=20commit=20SHA=20?= =?UTF-8?q?=F0=9F=94=97=20https://github.com/anysphere/asphr/commit/cd3714?= =?UTF-8?q?5a6c83c0f7d32120fb8d00950d66145c84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WORKSPACE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WORKSPACE b/WORKSPACE index c20d2d3a..256fdaff 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -7,7 +7,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") git_repository( name = "asphr", - commit = "209b037166f2d9bc4de62daf80a00ea1f0cde0fd", # autoupdate anysphere/asphr + commit = "cd37145a6c83c0f7d32120fb8d00950d66145c84", # autoupdate anysphere/asphr init_submodules = True, remote = "https://github.com/anysphere/asphr.git", )