use chrono::{NaiveDateTime, Utc};
use tari_comms::types::CommsPublicKey;
use crate::{
schema::{contacts, received_messages, sent_messages, settings},
text_message_service::error::TextMessageError,
types::HashDigest,
};
use diesel::{
dsl::count,
prelude::*,
query_dsl::RunQueryDsl,
r2d2::{ConnectionManager, PooledConnection},
result::Error as DieselError,
SqliteConnection,
};
use digest::Digest;
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, convert::TryFrom};
use tari_comms::connection::NetAddress;
use tari_crypto::tari_utilities::{
byte_array::ByteArray,
hex::{from_hex, Hex},
};
pub fn generate_id<D: Digest>(
source_pub_key: &CommsPublicKey,
dest_pub_key: &CommsPublicKey,
message: &String,
timestamp: &NaiveDateTime,
index: usize,
) -> Vec<u8>
{
D::new()
.chain(source_pub_key.as_bytes())
.chain(dest_pub_key.as_bytes())
.chain(message.as_bytes())
.chain(timestamp.to_string())
.chain(index.to_le_bytes())
.result()
.to_vec()
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct SentTextMessage {
pub id: Vec<u8>,
pub source_pub_key: CommsPublicKey,
pub dest_pub_key: CommsPublicKey,
pub message: String,
pub timestamp: NaiveDateTime,
pub acknowledged: bool,
pub is_read: bool,
}
#[derive(Insertable, Queryable)]
#[table_name = "sent_messages"]
struct SentTextMessageSql {
pub id: String,
pub source_pub_key: String,
pub dest_pub_key: String,
pub message: String,
pub timestamp: NaiveDateTime,
pub acknowledged: i32,
pub is_read: i32,
}
impl SentTextMessage {
pub fn new(
source_pub_key: CommsPublicKey,
dest_pub_key: CommsPublicKey,
message: String,
index: Option<usize>,
) -> SentTextMessage
{
let timestamp = Utc::now().naive_utc();
let id = generate_id::<HashDigest>(&source_pub_key, &dest_pub_key, &message, ×tamp, index.unwrap_or(0));
SentTextMessage {
id,
source_pub_key,
dest_pub_key,
message,
timestamp,
acknowledged: false,
is_read: false,
}
}
pub fn commit(&self, conn: &PooledConnection<ConnectionManager<SqliteConnection>>) -> Result<(), TextMessageError> {
diesel::insert_into(sent_messages::table)
.values(SentTextMessageSql::from(self.clone()))
.execute(conn)?;
Ok(())
}
pub fn find(
id: &Vec<u8>,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<SentTextMessage, TextMessageError>
{
SentTextMessage::try_from(
sent_messages::table
.filter(sent_messages::id.eq(id.to_hex()))
.first::<SentTextMessageSql>(conn)?,
)
}
pub fn find_by_dest_pub_key(
dest_pub_key: &CommsPublicKey,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<Vec<SentTextMessage>, TextMessageError>
{
let messages = sent_messages::table
.filter(sent_messages::dest_pub_key.eq(dest_pub_key.to_hex()))
.order_by(sent_messages::timestamp)
.load::<SentTextMessageSql>(conn)?;
let mut result: Vec<SentTextMessage> = Vec::new();
for m in messages {
result.push(SentTextMessage::try_from(m)?);
}
Ok(result)
}
pub fn index(
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<Vec<SentTextMessage>, TextMessageError> {
let messages = sent_messages::table.load::<SentTextMessageSql>(conn)?;
let mut result: Vec<SentTextMessage> = Vec::new();
for m in messages {
result.push(SentTextMessage::try_from(m)?);
}
Ok(result)
}
pub fn count_by_dest_pub_key(
dest_pub_key: &CommsPublicKey,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<i64, TextMessageError>
{
Ok(sent_messages::table
.filter(sent_messages::dest_pub_key.eq(dest_pub_key.to_hex()))
.select(count(sent_messages::dest_pub_key))
.first(conn)?)
}
pub fn mark_sent_message_ack(
id: &Vec<u8>,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<(), TextMessageError>
{
let num_updated = diesel::update(sent_messages::table.filter(sent_messages::id.eq(&id.to_hex())))
.set(UpdateAckSentTextMessage {
acknowledged: Some(1i32),
})
.execute(conn)?;
if num_updated == 0 {
return Err(TextMessageError::DatabaseUpdateError);
}
Ok(())
}
pub fn mark_sent_message_opened(
id: Vec<u8>,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<(), TextMessageError>
{
let num_updated = diesel::update(sent_messages::table.filter(sent_messages::id.eq(&id.to_hex())))
.set(UpdateOpenedSentTextMessage { is_read: Some(1i32) })
.execute(conn)?;
if num_updated == 0 {
return Err(TextMessageError::DatabaseUpdateError);
}
Ok(())
}
}
impl From<SentTextMessage> for SentTextMessageSql {
fn from(msg: SentTextMessage) -> SentTextMessageSql {
SentTextMessageSql {
id: msg.id.to_hex(),
source_pub_key: msg.source_pub_key.to_hex(),
dest_pub_key: msg.dest_pub_key.to_hex(),
message: msg.message,
timestamp: msg.timestamp,
acknowledged: msg.acknowledged as i32,
is_read: msg.is_read as i32,
}
}
}
impl TryFrom<SentTextMessageSql> for SentTextMessage {
type Error = TextMessageError;
fn try_from(msg: SentTextMessageSql) -> Result<Self, Self::Error> {
Ok(SentTextMessage {
id: from_hex(msg.id.as_str())?,
source_pub_key: CommsPublicKey::from_hex(msg.source_pub_key.as_str())?,
dest_pub_key: CommsPublicKey::from_hex(msg.dest_pub_key.as_str())?,
message: msg.message,
timestamp: msg.timestamp,
acknowledged: msg.acknowledged != 0,
is_read: msg.is_read != 0,
})
}
}
#[derive(AsChangeset)]
#[table_name = "sent_messages"]
pub struct UpdateAckSentTextMessage {
pub acknowledged: Option<i32>,
}
#[derive(AsChangeset)]
#[table_name = "sent_messages"]
pub struct UpdateOpenedSentTextMessage {
pub is_read: Option<i32>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ReceivedTextMessage {
pub id: Vec<u8>,
pub source_pub_key: CommsPublicKey,
pub dest_pub_key: CommsPublicKey,
pub message: String,
pub timestamp: NaiveDateTime,
}
#[derive(Queryable, Insertable)]
#[table_name = "received_messages"]
struct ReceivedTextMessageSql {
pub id: Vec<u8>,
pub source_pub_key: String,
pub dest_pub_key: String,
pub message: String,
pub timestamp: NaiveDateTime,
}
impl ReceivedTextMessage {
pub fn commit(&self, conn: &PooledConnection<ConnectionManager<SqliteConnection>>) -> Result<(), TextMessageError> {
diesel::insert_into(received_messages::table)
.values(ReceivedTextMessageSql::from(self.clone()))
.execute(conn)?;
Ok(())
}
pub fn index(
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<Vec<ReceivedTextMessage>, TextMessageError> {
let messages = received_messages::table.load::<ReceivedTextMessageSql>(conn)?;
let mut result: Vec<ReceivedTextMessage> = Vec::new();
for m in messages {
result.push(ReceivedTextMessage::try_from(m)?);
}
Ok(result)
}
pub fn find(
id: &Vec<u8>,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<ReceivedTextMessage, TextMessageError>
{
ReceivedTextMessage::try_from(
received_messages::table
.filter(received_messages::id.eq(id))
.first::<ReceivedTextMessageSql>(conn)?,
)
}
pub fn find_by_source_pub_key(
source_pub_key: &CommsPublicKey,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<Vec<ReceivedTextMessage>, TextMessageError>
{
let messages = received_messages::table
.filter(received_messages::source_pub_key.eq(source_pub_key.to_hex()))
.order_by(received_messages::timestamp)
.load::<ReceivedTextMessageSql>(conn)?;
let mut result: Vec<ReceivedTextMessage> = Vec::new();
for m in messages {
result.push(ReceivedTextMessage::try_from(m)?);
}
Ok(result)
}
}
impl From<ReceivedTextMessage> for ReceivedTextMessageSql {
fn from(msg: ReceivedTextMessage) -> ReceivedTextMessageSql {
ReceivedTextMessageSql {
id: msg.id,
source_pub_key: msg.source_pub_key.to_hex(),
dest_pub_key: msg.dest_pub_key.to_hex(),
message: msg.message,
timestamp: msg.timestamp,
}
}
}
impl TryFrom<ReceivedTextMessageSql> for ReceivedTextMessage {
type Error = TextMessageError;
fn try_from(msg: ReceivedTextMessageSql) -> Result<Self, Self::Error> {
Ok(ReceivedTextMessage {
id: msg.id,
source_pub_key: CommsPublicKey::from_hex(msg.source_pub_key.as_str())?,
dest_pub_key: CommsPublicKey::from_hex(msg.dest_pub_key.as_str())?,
message: msg.message,
timestamp: msg.timestamp,
})
}
}
impl From<ReceivedTextMessage> for SentTextMessage {
fn from(t: ReceivedTextMessage) -> SentTextMessage {
SentTextMessage {
id: t.id,
source_pub_key: t.source_pub_key,
dest_pub_key: t.dest_pub_key,
message: t.message,
timestamp: t.timestamp,
acknowledged: false,
is_read: false,
}
}
}
impl From<SentTextMessage> for ReceivedTextMessage {
fn from(t: SentTextMessage) -> ReceivedTextMessage {
ReceivedTextMessage {
id: t.id,
source_pub_key: t.source_pub_key,
dest_pub_key: t.dest_pub_key,
message: t.message,
timestamp: t.timestamp,
}
}
}
impl PartialOrd<ReceivedTextMessage> for ReceivedTextMessage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.timestamp.partial_cmp(&other.timestamp)
}
}
impl Ord for ReceivedTextMessage {
fn cmp(&self, other: &Self) -> Ordering {
self.timestamp.cmp(&other.timestamp)
}
}
impl PartialOrd<SentTextMessage> for SentTextMessage {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.timestamp.partial_cmp(&other.timestamp)
}
}
impl Ord for SentTextMessage {
fn cmp(&self, other: &Self) -> Ordering {
self.timestamp.cmp(&other.timestamp)
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Contact {
pub screen_name: String,
pub pub_key: CommsPublicKey,
pub address: NetAddress,
}
#[derive(Queryable, Insertable)]
#[table_name = "contacts"]
struct ContactSql {
pub pub_key: String,
pub screen_name: String,
pub address: String,
}
impl Contact {
pub fn new(screen_name: String, pub_key: CommsPublicKey, address: NetAddress) -> Contact {
Contact {
screen_name,
pub_key,
address,
}
}
pub fn commit(&self, conn: &PooledConnection<ConnectionManager<SqliteConnection>>) -> Result<(), TextMessageError> {
diesel::insert_into(contacts::table)
.values(ContactSql::from(self.clone()))
.execute(conn)?;
Ok(())
}
pub fn index(
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<Vec<Contact>, TextMessageError> {
let contacts = contacts::table.load::<ContactSql>(conn)?;
let mut result: Vec<Contact> = Vec::new();
for c in contacts {
result.push(Contact::try_from(c)?);
}
Ok(result)
}
pub fn find(
pub_key: &CommsPublicKey,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<Contact, TextMessageError>
{
Ok(Contact::try_from(
contacts::table
.filter(contacts::pub_key.eq(pub_key.to_hex()))
.first::<ContactSql>(conn)?,
)?)
}
pub fn update(
&self,
updated_contact: UpdateContact,
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<Contact, TextMessageError>
{
let num_updated = diesel::update(contacts::table.filter(contacts::pub_key.eq(&self.pub_key.to_hex())))
.set(UpdateContactSql::from(updated_contact))
.execute(conn)?;
if num_updated == 0 {
return Err(TextMessageError::DatabaseUpdateError);
}
Ok(Contact::find(&self.pub_key, conn)?)
}
pub fn delete(&self, conn: &PooledConnection<ConnectionManager<SqliteConnection>>) -> Result<(), TextMessageError> {
let num_deleted =
diesel::delete(contacts::table.filter(contacts::pub_key.eq(&self.pub_key.to_hex()))).execute(conn)?;
if num_deleted == 0 {
return Err(TextMessageError::ContactNotFound);
}
Ok(())
}
}
impl From<Contact> for ContactSql {
fn from(c: Contact) -> ContactSql {
ContactSql {
screen_name: c.screen_name,
pub_key: c.pub_key.to_hex(),
address: format!("{}", c.address),
}
}
}
impl TryFrom<ContactSql> for Contact {
type Error = TextMessageError;
fn try_from(c: ContactSql) -> Result<Self, Self::Error> {
Ok(Contact {
screen_name: c.screen_name,
pub_key: CommsPublicKey::from_hex(c.pub_key.as_str())?,
address: c.address.parse()?,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct UpdateContact {
pub screen_name: Option<String>,
pub address: Option<NetAddress>,
}
#[derive(AsChangeset)]
#[table_name = "contacts"]
struct UpdateContactSql {
pub screen_name: Option<String>,
pub address: Option<String>,
}
impl From<UpdateContact> for UpdateContactSql {
fn from(c: UpdateContact) -> UpdateContactSql {
UpdateContactSql {
screen_name: c.screen_name,
address: c.address.map(|a| format!("{}", a)),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TextMessageSettings {
pub pub_key: CommsPublicKey,
pub screen_name: String,
}
#[derive(Debug, Queryable, Insertable)]
#[table_name = "settings"]
pub struct TextMessageSettingsSql {
pub_key: String,
screen_name: String,
}
impl TextMessageSettings {
pub fn new(screen_name: String, pub_key: CommsPublicKey) -> TextMessageSettings {
TextMessageSettings { screen_name, pub_key }
}
pub fn commit(&self, conn: &PooledConnection<ConnectionManager<SqliteConnection>>) -> Result<(), TextMessageError> {
conn.transaction::<_, DieselError, _>(|| {
diesel::delete(settings::table).execute(conn)?;
diesel::insert_into(settings::table)
.values(TextMessageSettingsSql::from(self.clone()))
.execute(conn)?;
Ok(())
})?;
Ok(())
}
pub fn read(
conn: &PooledConnection<ConnectionManager<SqliteConnection>>,
) -> Result<TextMessageSettings, TextMessageError> {
let read_settings = settings::table.load::<TextMessageSettingsSql>(conn)?;
let mut result: Vec<TextMessageSettings> = Vec::new();
for rs in read_settings {
result.push(TextMessageSettings::try_from(rs)?);
}
if result.len() != 1 {
return Err(TextMessageError::SettingsReadError);
}
Ok(result.remove(0))
}
}
impl From<TextMessageSettings> for TextMessageSettingsSql {
fn from(c: TextMessageSettings) -> TextMessageSettingsSql {
TextMessageSettingsSql {
screen_name: c.screen_name,
pub_key: c.pub_key.to_hex(),
}
}
}
impl TryFrom<TextMessageSettingsSql> for TextMessageSettings {
type Error = TextMessageError;
fn try_from(c: TextMessageSettingsSql) -> Result<Self, Self::Error> {
Ok(TextMessageSettings {
screen_name: c.screen_name,
pub_key: CommsPublicKey::from_hex(c.pub_key.as_str())?,
})
}
}
#[cfg(test)]
mod test {
use super::*;
use chrono::Utc;
use diesel::{Connection, SqliteConnection};
use std::path::PathBuf;
use tari_comms::types::CommsPublicKey;
use tari_crypto::keys::PublicKey;
fn get_path(name: Option<&str>) -> String {
let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("tests/data");
path.push(name.unwrap_or(""));
path.to_str().unwrap().to_string()
}
fn clean_up(name: &str) {
if std::fs::metadata(get_path(Some(name))).is_ok() {
std::fs::remove_file(get_path(Some(name))).unwrap();
}
}
fn init(name: &str) {
clean_up(name);
let path = get_path(None);
let _ = std::fs::create_dir(&path).unwrap_or_default();
}
#[test]
fn db_model_tests() {
let (_secret_key1, public_key1) = CommsPublicKey::random_keypair(&mut OsRng);
let (_secret_key2, public_key2) = CommsPublicKey::random_keypair(&mut OsRng);
let (_secret_key3, public_key3) = CommsPublicKey::random_keypair(&mut OsRng);
let (_secret_key4, public_key4) = CommsPublicKey::random_keypair(&mut OsRng);
let db_name = "test.sqlite3";
let db_path = get_path(Some(db_name));
init(db_name);
embed_migrations!("./migrations");
let conn = SqliteConnection::establish(&db_path).unwrap_or_else(|_| panic!("Error connecting to {}", db_path));
embedded_migrations::run_with_output(&conn, &mut std::io::stdout()).expect("Migration failed");
let manager = ConnectionManager::<SqliteConnection>::new(db_path);
let pool = diesel::r2d2::Pool::builder().max_size(1).build(manager).unwrap();
let conn = pool.get().unwrap();
conn.execute("PRAGMA foreign_keys = ON").unwrap();
let _settings1 = TextMessageSettings::new("Bob".to_string(), public_key1.clone()).commit(&conn);
let read_settings1 = TextMessageSettings::read(&conn).unwrap();
assert_eq!(read_settings1.screen_name, "Bob".to_string());
let _settings2 = TextMessageSettings::new("Ed".to_string(), public_key1.clone()).commit(&conn);
let read_settings2 = TextMessageSettings::read(&conn).unwrap();
assert_eq!(read_settings2.screen_name, "Ed".to_string());
let contact1 = Contact::new(
"Alice".to_string(),
public_key2.clone(),
"127.0.0.1:45532".parse().unwrap(),
);
contact1.commit(&conn).unwrap();
let contact2 = Contact::new(
"Bob".to_string(),
public_key3.clone(),
"127.0.0.1:45532".parse().unwrap(),
);
contact2.commit(&conn).unwrap();
let contact3 = Contact::new(
"Carol".to_string(),
public_key4.clone(),
"127.0.0.1:45537".parse().unwrap(),
);
assert!(contact3.clone().delete(&conn).is_err());
contact3.commit(&conn).unwrap();
let contacts = Contact::index(&conn).unwrap();
assert_eq!(contacts, vec![contact1.clone(), contact2.clone(), contact3.clone()]);
let update = UpdateContact {
screen_name: Some("Carol".to_string()),
address: None,
};
let contact1 = contact1.update(update, &conn).unwrap();
let contacts = Contact::index(&conn).unwrap();
assert_eq!(contacts, vec![contact1.clone(), contact2.clone(), contact3.clone()]);
assert_eq!(contact2, Contact::find(&contact2.pub_key.clone(), &conn).unwrap());
contact3.delete(&conn).unwrap();
let contacts = Contact::index(&conn).unwrap();
assert_eq!(contacts, vec![contact1.clone(), contact2.clone()]);
assert!(
SentTextMessage::new(public_key1.clone(), public_key1.clone(), "Test1".to_string(), Some(0))
.commit(&conn)
.is_err()
);
let sent_msg1 = SentTextMessage::new(public_key1.clone(), public_key2.clone(), "Test1".to_string(), Some(0));
sent_msg1.commit(&conn).unwrap();
let sent_msg2 = SentTextMessage::new(public_key1.clone(), public_key3.clone(), "Test2".to_string(), Some(0));
sent_msg2.commit(&conn).unwrap();
let sent_msg3 = SentTextMessage::new(public_key1.clone(), public_key3.clone(), "Test3".to_string(), Some(0));
sent_msg3.commit(&conn).unwrap();
let sent_msgs = SentTextMessage::index(&conn).unwrap();
assert_eq!(sent_msgs, vec![sent_msg1.clone(), sent_msg2.clone(), sent_msg3.clone()]);
let find1 = SentTextMessage::find(&sent_msg1.id, &conn).unwrap();
assert_eq!(find1, sent_msg1);
let find2 = SentTextMessage::find_by_dest_pub_key(&public_key3.clone(), &conn).unwrap();
assert_eq!(find2, vec![sent_msg2.clone(), sent_msg3.clone()]);
let count = SentTextMessage::count_by_dest_pub_key(&public_key3.clone(), &conn).unwrap();
assert_eq!(count, 2);
assert!(SentTextMessage::mark_sent_message_ack(&vec![2u8; 32], &conn).is_err());
SentTextMessage::mark_sent_message_ack(&sent_msg1.id.clone(), &conn).unwrap();
let find3 = SentTextMessage::find(&sent_msg1.id, &conn).unwrap();
assert!(find3.acknowledged);
assert!(SentTextMessage::mark_sent_message_opened(vec![2u8; 32], &conn).is_err());
SentTextMessage::mark_sent_message_opened(sent_msg1.id.clone(), &conn).unwrap();
let find4 = SentTextMessage::find(&sent_msg1.id, &conn).unwrap();
assert!(find4.acknowledged);
let recv_msg1 = ReceivedTextMessage {
id: vec![1u8; 32],
source_pub_key: public_key1.clone(),
dest_pub_key: public_key2.clone(),
message: "recv1".to_string(),
timestamp: Utc::now().naive_utc(),
};
recv_msg1.commit(&conn).unwrap();
let recv_msg2 = ReceivedTextMessage {
id: vec![2u8; 32],
source_pub_key: public_key2.clone(),
dest_pub_key: public_key3.clone(),
message: "recv2".to_string(),
timestamp: Utc::now().naive_utc(),
};
recv_msg2.commit(&conn).unwrap();
let recv_msg3 = ReceivedTextMessage {
id: vec![3u8; 32],
source_pub_key: public_key2.clone(),
dest_pub_key: public_key3.clone(),
message: "recv3".to_string(),
timestamp: Utc::now().naive_utc(),
};
recv_msg3.commit(&conn).unwrap();
let recv_msgs = ReceivedTextMessage::index(&conn).unwrap();
assert_eq!(recv_msgs, vec![recv_msg1.clone(), recv_msg2.clone(), recv_msg3.clone()]);
let find1 = ReceivedTextMessage::find(&recv_msg1.id, &conn).unwrap();
assert_eq!(find1, recv_msg1);
let find2 = ReceivedTextMessage::find_by_source_pub_key(&public_key2.clone(), &conn).unwrap();
assert_eq!(find2, vec![recv_msg2, recv_msg3]);
clean_up(db_name);
}
}